[讀書筆記]并發(fā)和競態(tài)(第五章)

綜述

并發(fā)問題是編程中經(jīng)常遇到的難題暮顺,我們需要學(xué)會針對并發(fā)產(chǎn)生的競態(tài)進(jìn)行編程

一、信號量和互斥體

Linux上信號量的實現(xiàn)

1.信號量:本質(zhì)上是一個整數(shù)值蝙茶,它和一對函數(shù)聯(lián)合使用,這對函數(shù)通常稱為P和V,希望進(jìn)入臨界區(qū)的進(jìn)程將在相關(guān)信號量上調(diào)用P油猫,如果信號量大于0,該值減1柠偶,進(jìn)程可以進(jìn)入臨界區(qū)代碼運行情妖;相反睬关,如果信號量的值<=0,進(jìn)程必須等待其他人釋放該信號量。解鎖信號量調(diào)用V函數(shù)毡证,該函數(shù)增加信號量的值电爹,并在必要時喚醒等待的進(jìn)程。

當(dāng)信號量用于互斥時(即避免多個進(jìn)程同時在一個臨界區(qū)中運行)
信號量應(yīng)初始化為1
這種信號量在任何時刻只能由單個進(jìn)程或者線程擁有料睛,這種模式下丐箩,
信號量也成為互斥體
2.聲明和初始化
void sema_init(strcut semaphore *sem,int val);
sem代表信號量,val代表初始值恤煞,一般初始化為1.
DECLARE_MUTEX(name):一個名稱為name的信號量被初始化為1
DECLARE_MUTEX_LOCKED(name):一個名稱為name的信號量被初始化為0

void init_MUTEX(stuct semaphore *sem):
void init_MUTEX_LOCKED(stuct semaphore *sem):
LINUX2.6版本后已經(jīng)被遺棄 無法使用

Linux中的P函數(shù)是down(),該函數(shù)會減少信號量的值屎勘,必要時會一直等待。
void down(struct semaphore *sem)
減少信號量的值 并在必要時一直等待居扒,操作不可中斷
void down_interruptible(struct semaphore *sem)
完成和down相同的工作概漱,但是操作是可中斷,通常推薦使用苔货,如果操作被中斷犀概,該函數(shù)返回非零值,而調(diào)用者不會擁有該信號夜惭,使用時注意檢查返回值姻灶,并且做出相應(yīng)的操作。
int down_trylock(struct semaphore *sem)
永遠(yuǎn)不會休眠诈茧,如果信號量在調(diào)用時不可獲得产喉,該函數(shù)會立即返回一個非零值。

當(dāng)一個函數(shù)成功調(diào)用上述的down函數(shù)敢会,就稱為該線程擁有(獲得曾沈、拿到)了該信號量,這樣該線程就被賦予訪問由該信號量保護(hù)的臨界區(qū)的權(quán)利鸥昏,當(dāng)互斥操作完成后塞俱,必須返回該信號量,即調(diào)用V函數(shù)

void up(strcut semaphore *sem);
調(diào)用后吏垮,調(diào)用者不在擁有該信號量障涯。

使用信號量實例

步驟1:定義信號量
在自己的定義的結(jié)構(gòu)體加入semaphore *sem;//互斥信號量
步驟2:初始化信號量
sema_init(sem,1);
默認(rèn)初始化信號量sem的值為1
步驟3:
在要保護(hù)的資源調(diào)用dowm_interruptible();
if(dowm_interruptible(&sem))
retrun -ERESTARTSYS;
//這里是要保護(hù)資源的代碼
out:
up(&sem);
在函數(shù)調(diào)用up最后釋放信號量

strcut hello_dev{
  int val;
  semaphore *sem;//互斥信號量
}
static hello_init()
{
//初始化信號量
sema_init(dev->sem,1);
}
/*讀取寄存器設(shè)備 val的值*/
static ssize_t hello_read(struct file *filp,char __user *buf,
size_t count,loff_t *f_ops) {
    ssize_t err = 0;
    struct hello_dev *dev = filp->private_data;

    /*同步訪問*/
    if(down_interruptible(&(dev->sem)));
        return -ERESTARTSYS;
    
    if(count < sizeof(dev->val)){
        goto out;
    }
    /*將寄存器val的值拷貝到用戶提供的緩存區(qū)*/
    if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){
        err = -EFAULT;
        goto out;
    }

    out:
    up(&(dev->sem));
    return err;
}

讀取者/寫入者信號量

許多任務(wù)可分為:
1.只需要讀取受保護(hù)的數(shù)據(jù)(多個進(jìn)程和線程可以同時并發(fā)訪問)
2.寫入受保護(hù)的數(shù)據(jù)
為此,Linux提供了特殊的信號量"rwsem"(或者reader/writer semaphore),rwsem使用很少膳汪,偶爾有用

相關(guān)定義包含在<linux/rwsem.h>頭文件中
struct rw_semaphore *sem;
初始化:
void init_rwsem(struct rw_semaphore *sem);

對于只讀訪問唯蝶,可用接口如下:
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);;
void up_read(struct rw_semaphore *sem);
對于寫入訪問,可用接口如下:
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
downgrade_write允許其他讀取者訪問

完成量

completion是一種輕量級的機制遗嗽,它允許一個線程告訴另一個線程某個工作已經(jīng)完成了粘我,包含在<linux/completion.h>中
1.創(chuàng)建和初始化completion
DECLARE_COMPLETION(my_com);
或者動態(tài)創(chuàng)建和初始化
strcut completion my_com;
inti_completion(&my_com);
2.等待completion
void wait_for_completion(strcut completion *c);
注意,該函數(shù)執(zhí)行一個非中斷等待痹换,如果代碼調(diào)用了該函數(shù)且沒人能完成該信號量征字,則會產(chǎn)生一個不可殺進(jìn)程都弹!
3.喚醒completion
void completion(struct completion *c);
void complete_all(struct completion *c);
快速重新初始化某個復(fù)用的completion
INIT_COMPLETION(struct completion c);

自旋鎖

信號量在互斥中是非常有用的工具,內(nèi)核還提供另一種工具--自旋鎖匙姜。
和信號量不同缔杉,自旋鎖可以在休眠的代碼中使用,如中斷處理例程搁料,正確使用的情況下或详,自旋鎖性能比信號量好!

自旋鎖API

初始化
自旋鎖相關(guān)定義包含在頭文件<linux/spinlock.h>中
編譯時初始化
strcuvt spinlock_t my_lock;
my_lock = SPIN_LOCK_UNLOCKED;
或者
運行時初始化
void spin_lock_init(spinlock_t *lock);
進(jìn)入臨界區(qū)之前郭计,必須調(diào)用以下函數(shù)獲取鎖
void spin_lock(spinlock_t *lock);
注意:所以自旋鎖本質(zhì)上都是不可中斷的霸琴,一旦調(diào)用了spin_lock,在獲取鎖之前一直處于自旋狀態(tài)

釋放鎖函數(shù)
void spin_unlock(spinlock_t *lock);

注意:為了避免
在中斷例程自旋時昭伸,非中斷代碼將沒有機會釋放這種個自旋鎖梧乘,導(dǎo)致處理器將永遠(yuǎn)自旋下去的情況
我們需要在擁有自旋鎖時禁止中斷(僅本地CPU上),下面的函數(shù)可以實現(xiàn)用于禁止中斷的自旋鎖函數(shù)庐杨。
另一個重要原則:自旋轉(zhuǎn)必須的在盡可能短的時間內(nèi)擁有选调!

自旋鎖函數(shù)
void spin_lock(spinlock_t *lock):允許中斷的自旋鎖
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags)
在獲得自旋鎖之前禁止中斷(只在本地cup上),先前中斷報錯在flags中
void spin_lock_irq(spinlock_t *lock)
如果我們能確保沒有任何其他代碼禁止本地處理器的中斷,則可以使用spin_lock_irq灵份,而無需跟蹤標(biāo)志仁堪!
void spin_lock_bh(spinlock_t *lock)
在獲得鎖之前禁止軟件中斷,允許硬件中斷打開
該函數(shù)可以安全的避免死鎖問題填渠,還能服務(wù)硬件中斷
釋放鎖函數(shù)

void spin_unlock(spinlock_t *lock):
void spin_unlock_irqrestore(spinlock_t *lock,unsigned long flags)

void spin_unlock_irq(spinlock_t *lock)
void spin_unlock_bh(spinlock_t *lock)

非阻塞式自旋鎖
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
兩個函數(shù)在成功獲取自旋鎖時弦聂,返回非零值,失敗時返回零氛什,對應(yīng)禁止中斷的情況沒有對應(yīng)的try版本
讀取者/寫入者自旋鎖
和rwsem信號量很相似莺葫,但是可能會造成讀取者饑餓,導(dǎo)致性能變低枪眉!

注意防止死鎖情況
死鎖1:當(dāng)某個獲得鎖的函數(shù)要調(diào)用其他同樣試圖獲取這個鎖的函數(shù)捺檬,代碼就會死鎖,無論是信號量還是自旋鎖贸铜,都不允許鎖擁有者第二次獲得這個鎖

死鎖2:線程1擁有鎖1堡纬,線程2擁有鎖2,這時候萨脑,當(dāng)這兩個線程都試圖獲取另外線程的的鎖時隐轩,這兩個線程將處于死鎖狀態(tài)
最好的辦法是避免同時需要多個鎖的情況

鎖之外的辦法

1.免鎖算法

常用于免鎖算法的生產(chǎn)者/消費者任務(wù)的數(shù)據(jù)結(jié)構(gòu)就是循環(huán)緩沖區(qū)饺饭!

2.原子變量

共享資源是一個簡單的整數(shù)型時渤早,內(nèi)核提供了一種原子的整數(shù)類型
稱為atomic_t 定義在<asm/atomic.h>中
初始化
void atomic_set(atomic *v,int t);
或者
atomic_t v = ATOMIC_INIT(0);初始化為0
還有讀寫函數(shù),運算操作函數(shù)瘫俊,位操作函數(shù)就不一一列舉了

3.seqlock

當(dāng)要保護(hù)的資源很小鹊杖、很簡單悴灵、會被頻繁讀取訪問且寫入訪問很少發(fā)生且必須快速時,就可以使用內(nèi)核提供的seqlock
允許讀取者自由訪問骂蓖,但是需要讀取者檢測是否和寫入者沖突
seqlock通常不能包含在含有指針的數(shù)據(jù)結(jié)構(gòu)中积瞒,因為在寫入者修改數(shù)據(jù)結(jié)構(gòu)的同時,讀取者可能會追隨一個無效的指針登下。

seqlock定義在<linux/seqlock.h>
初始化方法2種
1.seqlock_t lock1 = SEQLOCK_UNLOCKED;
2.seqlock_t lock2;
seqlock_init(&lock2);

讀取時會訪問通過一個(無符號的)整數(shù)順序值而進(jìn)入臨界區(qū)茫孔,在退出時,該順序值和當(dāng)前值比較被芳,如果不相等缰贝,必須重試讀取訪問。

unsigned int seq;
do {
  seq = read_seqbegin(&the_lock);
  /*完成需要做的工作*/
}while read_seqretry(&the_lock,seq);

如果在中斷處理例程中使用seqlock,則應(yīng)該使用IRQ安全的版本

unsigned int read_seqbegin_irqsave(seqlock_t *lock,unsigned long flags);
int read_seqretry_irqrestore(seqlock_t *lock,unsigned int seq,
                                                        unsigned long flags);

寫入者必須進(jìn)入由seqlock包含的臨界區(qū)時獲得一個互斥鎖畔濒,因此要調(diào)用以下函數(shù):
void write_sequnlock(seqlock_t *lock);
還有其他常見自旋鎖的變種函數(shù)

void write_seqlock_irqsave(seqlock_t *lock,unsigned long flags);
void write_seqlock_irq(seqlock_t *lock);
void write_seqlock_bh(seqlock_t *lock);

void write_sequnlock_irqrestore(seqlock_t *lock,unsigned long flags);
void write_sequnlock_irq(seqlock_t *lock);
void write_sequnlock_bh(seqlock_t *lock);

另外
如果write_tryseqlock()可以獲得自旋鎖剩晴,它也會返回非0值。

讀取-復(fù)制-更新(read-copy-update,RCU)

RCU是一種高級的互斥機制侵状,正確使用下赞弥,也可獲得很高的性能,但很少在驅(qū)動程序中使用趣兄。
RCU針對經(jīng)常發(fā)生讀取而很少寫入的情形做了優(yōu)化绽左,被保護(hù)的資源應(yīng)通過指針訪問,而對這些資源的引用必須由原子代碼擁有艇潭!

#include <linux/rcupdate.h>
使用讀取-復(fù)制-更新(RCU)機制是需要包含的頭文件
void rcu_read_lock();
void rcu_read_unlock();
獲取對受RCU保護(hù)資源的讀取訪問的宏
void call_rcu(srcut rcu_head head,void (func)(void *arg),void *arg);
準(zhǔn)備用于安全示范受RCU保護(hù)的資源的回調(diào)函數(shù)妇菱,該函數(shù)將在所有的處理器被調(diào)度后運行!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暴区,一起剝皮案震驚了整個濱河市闯团,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌仙粱,老刑警劉巖房交,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伐割,居然都是意外死亡候味,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門隔心,熙熙樓的掌柜王于貴愁眉苦臉地迎上來白群,“玉大人,你說我怎么就攤上這事硬霍≈穆” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長粱玲。 經(jīng)常有香客問我躬柬,道長,這世上最難降的妖魔是什么抽减? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任允青,我火速辦了婚禮,結(jié)果婚禮上卵沉,老公的妹妹穿的比我還像新娘颠锉。我一直安慰自己,他們只是感情好史汗,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布木柬。 她就那樣靜靜地躺著,像睡著了一般淹办。 火紅的嫁衣襯著肌膚如雪眉枕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天怜森,我揣著相機與錄音速挑,去河邊找鬼。 笑死副硅,一個胖子當(dāng)著我的面吹牛姥宝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恐疲,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼腊满,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了培己?” 一聲冷哼從身側(cè)響起碳蛋,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎省咨,沒想到半個月后肃弟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡零蓉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年笤受,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敌蜂。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡箩兽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出章喉,到底是詐尸還是另有隱情汗贫,我是刑警寧澤身坐,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站芳绩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏撞反。R本人自食惡果不足惜妥色,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遏片。 院中可真熱鬧嘹害,春花似錦、人聲如沸吮便。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓需。三九已至许师,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僚匆,已是汗流浹背微渠。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咧擂,地道東北人逞盆。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像松申,于是被迫代替她去往敵國和親云芦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容