熱線電話:0755-23712116
郵箱:contact@shuangyi-tech.com
地(dì / de)址:深圳市寶安區沙井街道(dào)後亭茅洲山工業園工業大(dà)廈全至科技創新園科創大(dà)廈2層2A
信号量用來(lái)幹嘛的(de)呢?搜尋答案的(de)話,很多人(rén)都會告訴你主要(yào / yāo)用于(yú)線程同步的(de),意思就(jiù)是(shì)線程通信的(de)。簡單來(lái)說(shuō),比如我運行了(le/liǎo)2個(gè)線程A和(hé / huò)B,但是(shì)我希望B線程在(zài)A線程之(zhī)前執行,那麽我們就(jiù)可以(yǐ)用信号量來(lái)處理。有些人(rén)可能會疑惑,那麽麻煩幹嘛?你不(bù)是(shì)要(yào / yāo)B線程先執行嗎?那麽我讓A線程休眠一(yī / yì /yí)點時(shí)間不(bù)就(jiù)可以(yǐ)了(le/liǎo)嗎?沒錯,這(zhè)個(gè)思路是(shì)可以(yǐ)的(de),但是(shì)如果B線程也(yě)因爲(wéi / wèi)某些原因(比如硬件,操作系統的(de)原因)導緻延緩執行了(le/liǎo),這(zhè)該怎麽辦?到(dào)底A線程該休眠多少時(shí)間合适呢?所以(yǐ)正确的(de)做法就(jiù)是(shì)在(zài)B線程阻塞,A線程去喚醒這(zhè)個(gè)阻塞線程。
看到(dào)這(zhè)兒,看過我前面文章的(de)朋友可能一(yī / yì /yí)眼就(jiù)看出(chū)來(lái)了(le/liǎo)這(zhè)個(gè)不(bù)就(jiù)是(shì)前面講的(de)生産消費者模型提到(dào)的(de)用法嗎?
沒錯,信号量的(de)實現也(yě)是(shì)靠條件變量和(hé / huò)互斥鎖。
所以(yǐ)雖然C++中并沒有在(zài)語言級别上(shàng)支持信号量,但同樣的(de)我們可以(yǐ)利用以(yǐ)上(shàng)兩個(gè)來(lái)自己實現一(yī / yì /yí)個(gè)。
這(zhè)裏我也(yě)不(bù)得不(bù)提一(yī / yì /yí)句,條件變量和(hé / huò)互斥鎖組合使用真的(de)非常強大(dà),生産消費者模型中用到(dào)了(le/liǎo),線程池中用到(dào)了(le/liǎo),現在(zài)說(shuō)的(de)信号量也(yě)用到(dào)了(le/liǎo),所以(yǐ)大(dà)家一(yī / yì /yí)定要(yào / yāo)好好掌握條件變量和(hé / huò)互斥鎖的(de)使用,它們倆是(shì)你在(zài)多線程世界中縱橫捭阖的(de)利劍。
那麽我們如何用C++來(lái)實現一(yī / yì /yí)個(gè)信号量呢?
#ifndef _SEMAPHORE_H
#define _SEMAPHORE_H
#include <mutex>
#include <condition_variable>
using namespace std;
class Semaphore
{
public:
Semaphore(long count = 0) : count(count) {}
//V操作,喚醒
void signal()
{
unique_lock<mutex> unique(mt);
++count;
if (count <= 0)
cond.notify_one();
}
//P操作,阻塞
void wait()
{
unique_lock<mutex> unique(mt);
--count;
if (count < 0)
cond.wait(unique);
}
private:
mutex mt;
condition_variable cond;
long count;
};
#endif
信号量裏面用到(dào)了(le/liǎo)一(yī / yì /yí)個(gè)叫PV操作的(de)東西,P操作時(shí)阻塞,一(yī / yì /yí)般用wait()函數,V操作是(shì)喚醒,一(yī / yì /yí)般用singal()函數,至于(yú)不(bù)叫WS操作,反而(ér)爲(wéi / wèi)什麽叫PV操作呢?網上(shàng)說(shuō)是(shì)因爲(wéi / wèi)提出(chū)這(zhè)一(yī / yì /yí)系統方法的(de)人(rén)狄克斯特拉用荷蘭文定義的(de),因爲(wéi / wèi)在(zài)荷蘭文中,通過叫passeren,釋放叫vrijgeven,PV操作因此得名。對我們來(lái)說(shuō),這(zhè)些也(yě)沒有太大(dà)的(de)意義,記住這(zhè)些定義就(jiù)好了(le/liǎo),畢竟定義這(zhè)種東西,是(shì)不(bù)以(yǐ)我們的(de)意志爲(wéi / wèi)轉移的(de)。
寫好了(le/liǎo)信号量的(de)接口,那我們如何使用這(zhè)個(gè)信号量呢?這(zhè)個(gè)就(jiù)需要(yào / yāo)我們在(zài)外部寫一(yī / yì /yí)個(gè)多線程的(de)調用函數來(lái)調用。
#include "semaphore.h"
#include <thread>
#include <iostream>
using namespace std;
Semaphore sem(0);
void funA()
{
sem.wait();
//do something
cout << "funA" << endl;
}
void funB()
{
this_thread::sleep_for(chrono::seconds(1));
//do something
cout << "funB" << endl;
sem.signal();
}
int main()
{
thread t1(funA);
thread t2(funB);
t1.join();
t2.join();
}
這(zhè)裏我們想讓funB線程運行,然後再運行funA,多線程是(shì)通過時(shí)間片輪詢來(lái)執行的(de)。
假設先開始跑funA,執行到(dào)sem.wait()的(de)時(shí)候,進入wait函數可知,count減1,小于(yú)0,會發生阻塞,等待其他(tā)線程喚醒。
然後就(jiù)會切換到(dào)funB,這(zhè)裏即使休眠了(le/liǎo)1秒也(yě)不(bù)會切換到(dào)funA,因爲(wéi / wèi)那邊阻塞了(le/liǎo),沒有其他(tā)線程喚醒的(de)話就(jiù)會一(yī / yì /yí)直阻塞。funB休眠完之(zhī)後,就(jiù)會打印出(chū)結果,然後執行sem.signal(),進入signal函數可知,count加1,小于(yú)等于(yú)0,會喚醒其他(tā)阻塞的(de)線程。
然後再切到(dào)funA,執行後面的(de)操作,打印出(chū)結果。
所以(yǐ)這(zhè)個(gè)就(jiù)一(yī / yì /yí)定能保證funB先執行,funA後執行。當然前提是(shì)初始化信号量對象的(de)時(shí)候,要(yào / yāo)初始化爲(wéi / wèi)0。
Semaphore sem(0);
信号量用在(zài)多線程多任務同步的(de),一(yī / yì /yí)個(gè)線程完成了(le/liǎo)某一(yī / yì /yí)個(gè)動作就(jiù)通過信号量告訴别的(de)線程,别的(de)線程再進行某些動作。像這(zhè)裏funB完成任務之(zhī)後就(jiù)通過信号量的(de)PV操作告訴funA線程可以(yǐ)開始任務了(le/liǎo)。
最後需要(yào / yāo)注意的(de)是(shì),信号量不(bù)僅可以(yǐ)用于(yú)進程也(yě)可用于(yú)線程,它比條件變量要(yào / yāo)複雜很多,條件變量僅限于(yú)線程内使用,至于(yú)進程間如何使用信号量通信,後期我們在(zài)讨論。