Linux Qt使用POSIX多執行緒條件變數、互斥鎖(量)

真的好多巧合發表於2019-08-01

今天團建,但是文章也要寫。酒要喝好,文要寫美,方為我輩程式設計師的全才之路。嘎嘎

 

之前一直在看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

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

歡迎大家一起交流!

 

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

 

相關文章