今天團建,但是文章也要寫。酒要喝好,文要寫美,方為我輩程序員的全才之路。嘎嘎
之前一直在看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
線程正在運行時就被破壞了,這個我們接下來會說,那就是如何退出線程、終止線程以及取消線程等操作了。
歡迎大家一起交流!
如果轉載,請注明出處,禁止商業用途,感謝合作。