今天團建,但是文章也要寫。酒要喝好,文要寫美,方為我輩程式設計師的全才之路。嘎嘎
之前一直在看POSIX的多執行緒程式設計,上個週末結合自己的理解,寫了一個基於Qt的用條件變數同步執行緒的例子。故此來和大家一起分享,希望和大家一起交流。
提到執行緒,如果在UI程式設計中,總會和一些耗時操作聯絡在一起。Qt中處理耗時操作通常有兩種方式,一種是將耗時操作放線上程中;另一種則是使用QApplication::processEvents(),防止阻塞UI。從更加通用的角度來講,我是更傾向於執行緒的,但對於很多初學者來講,執行緒還是有一定難度的。比如說需要對執行緒間共享的資料提供保護,使用互斥量同步、使用條件變數、使用讀寫鎖同步等;各種同步方式用在什麼情況下,開始程式設計時多執行緒使用的並不多,無法切身體會到這些問題,後來程式寫的多了一點兒,慢慢接觸到一些多執行緒的東西,並且自己也可以學習了相關知識,並用到實際程式中。好了,下面以一個實際的例子為背景,來說明Linux POSIX多執行緒的一些特性。
程式環境:ubuntu 14.04、 Qt 5.5.1、 Posix多執行緒(C的用法)
這裡簡單說下我為什麼用Linux C的多執行緒,因為Qt的多程式設計對於一些執行緒的終止時含糊不清楚的,並且一個執行緒被終止後的資源是無法被清理的,所以我選擇是相對底層的一些用法,以後有機會我還會新增執行緒取消和執行緒退出的操作。
我自己設定的場景是這樣的,在UI主執行緒中通過介面手動向一個執行緒間共享的佇列中push資料,而另外開啟的一個執行緒則一直在while中pop資料,這算是一個變種的生產者和消費者模式吧。
至於條件變數、互斥量(也就是互斥鎖)的初始化在這裡不再詳細說明,只說明一些相對重要的地方。
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資料
該執行緒使用的是Qt的moveToThread方法建立的執行緒,這裡注意的是,整個類都執行在新的執行緒中。該槽函式隨著執行緒的啟動訊號(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
執行緒正在執行時就被破壞了,這個我們接下來會說,那就是如何退出執行緒、終止執行緒以及取消執行緒等操作了。
歡迎大家一起交流!
如果轉載,請註明出處,禁止商業用途,感謝合作。