Linux Qt使用POSIX多線程條件變量、互斥鎖(量)

今天團建,但是文章也要寫。酒要喝好,文要寫美,方爲我輩程序員的全才之路。嘎嘎

 

之前一直在看POSIX的多線程編程,上個週末結合自己的理解,寫了一個基於Qt的用條件變量同步線程的例子。故此來和大家一起分享,希望和大家一起交流。

 

提到線程,如果在UI編程中,總會和一些耗時操作聯繫在一起。Qt中處理耗時操作通常有兩種方式,一種是將耗時操作放在線程中;另一種則是使用QApplication::processEvents(),防止阻塞UI。從更加通用的角度來講,我是更傾向於線程的,但對於很多初學者來講,線程還是有一定難度的。比如說需要對線程間共享的數據提供保護,使用互斥量同步、使用條件變量、使用讀寫鎖同步等;各種同步方式用在什麼情況下,開始編程時多線程使用的並不多,無法切身體會到這些問題,後來程序寫的多了一點兒,慢慢接觸到一些多線程的東西,並且自己也可以學習了相關知識,並用到實際程序中。好了,下面以一個實際的例子爲背景,來說明Linux POSIX多線程的一些特性。

 

程序環境:ubuntu 14.04Qt 5.5.1Posix多線程(C的用法)

這裏簡單說下我爲什麼用Linux C的多線程,因爲Qt的多編程對於一些線程的終止時含糊不清楚的,並且一個線程被終止後的資源是無法被清理的,所以我選擇是相對底層的一些用法,以後有機會我還會添加線程取消和線程退出的操作。

 

我自己設定的場景是這樣的,在UI主線程中通過界面手動向一個線程間共享的隊列中push數據,而另外開啓的一個線程則一直在whilepop數據,這算是一個變種的生產者和消費者模式吧。

至於條件變量、互斥量(也就是互斥鎖)的初始化在這裏不再詳細說明,只說明一些相對重要的地方。

 

1. UI中向隊列push數據(生產者生產數)

這是一個槽函數,當在lineEdit中回車後,則會觸發該槽函數,由於該隊列是線程間的 共享數據,所以使用了互斥鎖進行保護,即該槽操作數據的過程中如果有其他線程想要操作數據,則其他線程則會被阻塞,即訪問一個已經被加鎖的互斥量的線程會被阻塞。

 

void Widget::on_le_writeNum_returnPressed()
{
    int status;

    status = pthread_mutex_lock (&mp_processThread->m_structCondition.mutex);
    if (status != 0)
        err_abort (status, "Lock mutex");

    QString num = ui->le_writeNum->text();
    mp_processThread->queuePushData(num.toInt());

    status = pthread_cond_signal (&mp_processThread->m_structCondition.cond);
//    status = pthread_cond_broadcast( &mp_processThread->m_structCondition.cond);
    if (status != 0)
        err_abort (status, "Signal condition");

    status = pthread_mutex_unlock (&mp_processThread->m_structCondition.mutex);
    if (status != 0)
        err_abort (status, "Unlock mutex");
}

 

  

2. 消費者線程pop數據

該線程使用的是QtmoveToThread方法創建的線程,這裏注意的是,整個類都運行在新的線程中。該槽函數隨着線程的啓動信號(start())發射後而一直進行while循環。首先對互斥量上鎖,之後判斷謂詞狀態,如果隊列爲空,則等待條件變量。等待條件變量時pthread_cond_wait()會自動釋放互斥鎖,這樣其他線程才能夠操作共享數據。從條件變量等待中醒來後,會再次獲得互斥鎖,以操作共享數據。共享數據被操作完成後,再次釋放互斥鎖。這是我們使用條件變量等待的一個操作流程,如果我們不使用條件變量等待會是怎樣的呢?

 

 

void ProcessThread::slot_processData()
{
    int status;

    while(!mb_stopThread)
    {
        status = pthread_mutex_lock (&m_structCondition.mutex);
        if (status != 0)
            err_abort (status, "Lock mutex");

        while(m_queue.empty())   //if queue is empty, wait contion
        {
//使用條件變量等待
            status = pthread_cond_wait(&m_structCondition.cond,
                                       &m_structCondition.mutex); 
//            qDebug() << "pthread_cond_wait is block func!";

            if (status != 0)
            {
                err_abort (status, "Wait on cond faild");
            }
        }

        while(!m_queue.empty())
        {
            qDebug() << "queue mem is" << m_queue.back();

            m_queue.pop();
        }

        status = pthread_mutex_unlock (&m_structCondition.mutex);
        if (status != 0)
            err_abort (status, "Unlock mutex");

    }

}

 

  

3. 不使用條件變量等待

①不使用條件變量等待

如果不使用條件變量等待,則消費者線程在很大一部時間內幾乎都是在執行 while(1)無限循環,這是很佔用CPU資源的,在ubuntu下,使用htop查看的效果 如下:

屏蔽status = pthread_cond_wait(&m_structCondition.cond,

&m_structCondition.mutex);

我們看到,此時CPU是滿負荷在運行的,這當然不是一個程序所應有的正常狀態。

 

②使用條件變量的結果

 

此時我們看到CPU的佔用率是很低的,這也是爲什麼使用條件變量的原因之一,讓不滿足的條件的線程掛起,而不是在浪費CPU資源。條件變量是 允許使用隊列的線程之間交換隊列狀態信息的機制。那麼當我們還沒有掌握線程條件變量的用法時,又遇到這種情況時,該怎麼做呢?簡單,加個5ms的延時即可,5ms對我們來講時間極短極短,但對計算機來講,已經挺長時間了。

 

最後,當我們關掉UI窗口時,會有這樣一句消息:

QThread: Destroyed while thread is still running

線程正在運行時就被破壞了,這個我們接下來會說,那就是如何退出線程、終止線程以及取消線程等操作了。

歡迎大家一起交流!

 

如果轉載,請註明出處,禁止商業用途,感謝合作。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章