首頁 > 軟體

C++11 condition_variable條件變數的用法說明

2022-07-11 18:00:59

1 什麼是條件變數

condition_variable是一個類,常和mutex搭配使用。

condition_variable類是一個同步原語,可用於阻塞一個執行緒或同時阻止多個執行緒,直到另一個執行緒修改共用變數並通知condition_variable。

防止多執行緒場景下,共用變數混亂。

理解條件變數要先理解三個概念:

  • 鎖 (鎖住共用變數,執行緒獨佔)
  • wait 等待 (等待通知條件變數,變化的共用變數是否滿足條件)
  • notify 通知 (通知等待的條件變數,共用變數傳送變化)

2 condition_variable類定義

2.1 wait函數

void wait( std::unique_lockstd::mutex& lock );
//Predicate是lambda表示式。
template< class Predicate >
void wait( std::unique_lockstd::mutex& lock, Predicate pred );
//以上二者都被notify_one())或notify_broadcast()喚醒,但是
//第二種方式是喚醒後也要滿足Predicate的條件。
//如果不滿足條件,繼續解鎖互斥量,然後讓執行緒處於阻塞或等待狀態。
//第二種等價於
while (!pred())
{
wait(lock);
}

3 condition_variable用法

condition_variable必定至少有兩方,一方是資源修改執行緒,一方是資源等待執行緒。就跟打籃球一樣,同時籃球只會在一個人手中,投籃後就釋放了籃球所有權,其他方就會搶奪籃球所有權。

3.1 資源修改執行緒步驟

  • 獲取一個mutex使用 std::unique_lock< std::mutex >
  • 保持鎖定狀態,修改共用變數
  • condition_variable物件執行notify_one或者notify_all(notify_one/notify_all執行前可以釋放鎖)

3.2 資源等待執行緒步驟

  • 獲取一個mutex使用 std::unique_lock< std::mutex > unlock用於保護要修改的共用變數
  • 檢查條件變數,

(1)條件變數滿足,執行緒繼續執行

(2)條件變數不滿足,wait會釋放unlock鎖,並掛起執行緒。

  • 當notify通知條件變數、超時過期或發生虛假喚醒時,執行緒被喚醒,互斥鎖unlock被原子地重新獲取。然後,執行緒應該檢查條件,如果喚醒是假的,則繼續等待

4 程式碼範例

4.1 無需notify場景

當wait第一次執行是,條件已經滿足,則程式不會阻塞(即無需notify),會直接向下執行。(僅為說明3.2 中第2點(1)的情況)

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::cout << "3、worker_thread子執行緒開始執行"  << endl;
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    std::cout << "4、worker_thread子執行緒獲取到鎖,條件滿足無需notify,不阻塞向下執行"  << endl;
    cv.wait(lk, []{return ready;});
 
    // after the wait, we own the lock.
    data += " after processing";
    // Send data back to main()
    processed = true;
    std::cout << "5、Worker thread signals data processing completedn";
 
    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    std::cout << "6、worker_thread子執行緒交出執行許可權,主執行緒執行"  << endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
    
    cv.notify_one();
    std::cout << "9、worker_thread呼叫 notify_one"  << endl;
}
int main()
{
    std::thread worker(worker_thread);
    std::cout << "1、主執行緒開始執行"  << std::endl;
    data = "Example data";
    // send data to the worker thread
    {
        //std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::lock_guard<std::mutex> lk(m);
        ready = true;
    }
    std::cout << "2、鎖已經釋放了,主執行緒休眠,子執行緒執行"  << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    //cv.notify_one();
    {
        std::cout << "7、主執行緒data:" << data << endl;
        std::unique_lock<std::mutex> lk(m);
        std::cout << "8、主執行緒條件滿足無需notify" << endl;
        cv.wait(lk, []{return processed;});
    }
    
    worker.join();
     std::cout << "10、主執行緒結束" << endl;
}

執行結果:

4.2 正常應用場景1

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::cout << "3、worker_thread子執行緒開始執行"  << endl;
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    std::cout << "4、worker_thread子執行緒獲取到鎖,條件不滿足,釋放lk鎖,子執行緒阻塞"  << endl;
    cv.wait(lk, []{return ready;});
    std::cout << "8、worker_thread子執行緒獲取到鎖,子執行緒繼續執行"  << endl;
    // after the wait, we own the lock.
    data += " after processing";
    // Send data back to main()
    processed = true;
    std::cout << "9、Worker thread signals data processing completedn";
 
    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    std::cout << "10、worker_thread呼叫 notify_one通知主執行緒執行"  << endl;
    cv.notify_one();
}
int main()
{
    std::thread worker(worker_thread);
    std::cout << "1、主執行緒開始執行"  << std::endl;
    data = "Example data";
    // send data to the worker thread
    {
        std::cout << "2、主執行緒休眠,子執行緒進入執行"  << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::cout << "5、主執行緒結束休眠,主執行緒獲取lk鎖,進入執行"  << std::endl;
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        
    }
    std::cout << "6、主執行緒釋放lk,呼叫notify通知子執行緒"  << std::endl;
    cv.notify_one();
    {
        std::cout << "7、由於主執行緒的執行時鐘週期未結束,繼續執行主執行緒獲取lk, wait檢查條件不滿足,釋放鎖" << endl;
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
   
    worker.join();
     std::cout << "11、主執行緒結束" << endl;
}

執行結果:

這裡notify執行後不一定立即執行子執行緒,如果cpu執行時鐘週期未結束,則主執行緒會繼續執行. 所以7,8,9,10順序可能變化參見4.3

同時4.1也會因為cpu時鐘週期,執行順序有所變動

4.3 正常應用場景2

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    std::cout << "3、worker_thread子執行緒開始執行"  << endl;
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    std::cout << "4、worker_thread子執行緒獲取到鎖,條件不滿足,釋放lk鎖,子執行緒阻塞"  << endl;
    cv.wait(lk, []{return ready;});
    std::cout << "8、worker_thread子執行緒獲取到鎖,子執行緒繼續執行"  << endl;
    // after the wait, we own the lock.
    data += " after processing";
    // Send data back to main()
    processed = true;
    std::cout << "9、Worker thread signals data processing completedn";
 
    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    std::cout << "10、worker_thread呼叫 notify_one通知主執行緒執行"  << endl;
    cv.notify_one();
}
int main()
{
    std::thread worker(worker_thread);
    std::cout << "1、主執行緒開始執行"  << std::endl;
    data = "Example data";
    // send data to the worker thread
    {
        std::cout << "2、主執行緒休眠,子執行緒進入執行"  << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::cout << "5、主執行緒結束休眠,主執行緒獲取lk鎖,進入執行"  << std::endl;
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        
    }
    std::cout << "6、主執行緒釋放lk,呼叫notify通知子執行緒"  << std::endl;
    cv.notify_one();
    {
        for(int i = 0; i< 10000000; i++)
        {
            int j = i;
        }
        std::cout << "7、由於主執行緒的執行時鐘週期未結束,繼續執行主執行緒獲取lk, wait檢查條件不滿足,釋放鎖" << endl;
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    
    worker.join();
    std::cout << "11、主執行緒結束" << endl;
}

執行結果:

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。 


IT145.com E-mail:sddin#qq.com