使用線程編寫程序需要技巧,而多線程的程序中的bug非常難以跟蹤、調(diào)試,因?yàn)檫@些bug經(jīng)常是難以再現(xiàn)的。
競(jìng)爭(zhēng)條件: 當(dāng)一個(gè)線程訪問一個(gè)數(shù)據(jù)結(jié)構(gòu)的時(shí)候,另一個(gè)線程也訪問同一個(gè)數(shù)據(jù)結(jié)構(gòu),這時(shí)就出現(xiàn)了競(jìng)爭(zhēng)條件——兩個(gè)線程(也可能是多個(gè))競(jìng)爭(zhēng)對(duì)同一個(gè)資源的訪問。 當(dāng)其中一個(gè)線程處理到一部分的時(shí)候,另外的線程可能進(jìn)入了對(duì)同一數(shù)據(jù)的處理,而且出于調(diào)度的原因,它運(yùn)行的比前一個(gè)更快;這時(shí),同樣的處理可能就出現(xiàn)了多次。 例如,一個(gè)代表任務(wù)列表的單向鏈表(隊(duì)列),一個(gè)線程從當(dāng)前元素中讀出了下一個(gè)任務(wù),并發(fā)現(xiàn)下一個(gè)任務(wù)不是空,準(zhǔn)備將此一個(gè)任務(wù)置為NULL并執(zhí)行任務(wù);這時(shí),調(diào)度使得這個(gè)線程停下來,另一個(gè)線程也從當(dāng)前元素中讀出了下一個(gè)任務(wù),當(dāng)然下個(gè)任務(wù)仍然不是空值,這個(gè)線程也將要執(zhí)行下一個(gè)任務(wù)。這樣,在某些不幸的情況下,這個(gè)任務(wù)被執(zhí)行了兩次。 更為不幸的情況下,執(zhí)行任務(wù)的過程中線程被中斷,此時(shí)另一個(gè)線程釋放了任務(wù)的內(nèi)存,那么執(zhí)行中的線程會(huì)導(dǎo)致段錯(cuò)誤。 在比較“幸運(yùn)”的情況下,這些事情可能從來也不發(fā)生;但是當(dāng)程序運(yùn)行在負(fù)荷很高的系統(tǒng)中時(shí),這個(gè)bug就會(huì)凸現(xiàn)出來。
互斥鎖: 為了排除競(jìng)爭(zhēng)條件的影響,應(yīng)該使一些操作變成“原子的”——這些操作既不能分割也不能中斷。 當(dāng)一個(gè)線程鎖定了互斥鎖后,其他線程也要求鎖定這個(gè)互斥鎖的時(shí)候,就會(huì)被阻塞;直到前面的線程解除鎖定后,其他線程才可以解除阻塞恢復(fù)運(yùn)行。 GNU/Linux保證線程在鎖定互斥鎖的過程中不會(huì)發(fā)生競(jìng)爭(zhēng)條件,只有一個(gè)線程可以鎖定互斥鎖,其他線程的鎖定請(qǐng)求都會(huì)被阻塞。
創(chuàng)建互斥鎖(Mutex),需要: `創(chuàng)建一個(gè)pthread_mutex_t類型的變量,將其指針傳入函數(shù)pthread_mutex_init中;該函數(shù)的第二個(gè)參數(shù)是指向一個(gè)mutex屬性對(duì)象(這個(gè)對(duì)象用來指定mutex的屬性)的指針。mutex對(duì)象只能初始化一次。 `更簡(jiǎn)單的辦法是,使用PTHREAD_MUTEX_INITIALIZER來獲得默認(rèn)屬性的mutex對(duì)象: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
使用mutex: `調(diào)用pthread_mutex_lock函數(shù),如果mutex對(duì)象未被鎖定,則此函數(shù)立即返回;如果已被鎖定,則此函數(shù)阻塞此線程的執(zhí)行,直到mutex被解除鎖定。 `每次解除鎖定后,只有一個(gè)線程可以解除阻塞恢復(fù)執(zhí)行,其他調(diào)用線程都會(huì)繼續(xù)阻塞。選定的解除阻塞的線程是不可預(yù)知的。 `調(diào)用pthread_mutex_unlock函數(shù),可以解除調(diào)用線程對(duì)mutex對(duì)象的鎖定。在鎖定mutex的線程中,必須調(diào)用此函數(shù)以解除鎖定。
mutex提供了解決競(jìng)爭(zhēng)條件的方案,但是它帶來了一種新的bug——死鎖(deadlock)。 所謂死鎖,就是說一個(gè)線程在等待永遠(yuǎn)不會(huì)發(fā)生的條件。 一個(gè)簡(jiǎn)單的死鎖:一個(gè)線程鎖定它自己已經(jīng)鎖定的mutex。這時(shí)程序的表現(xiàn)依賴于mutex的類型: `快速排他鎖(fast mutex)——導(dǎo)致死鎖。 `遞歸排他鎖(recursive mutex)——不導(dǎo)致死鎖。同一個(gè)線程可以安全的多次鎖定同一個(gè)已鎖定的mutex,但是鎖定的次數(shù)會(huì)被記錄下來,該線程還必須調(diào)用相應(yīng)次數(shù)的pthread_mutex_unlock才能真正解除對(duì)mutex的鎖定。 `檢錯(cuò)排他鎖(error-checking mutex)——GNU/Linux視此操作為死鎖,但是對(duì)鎖定的mutex調(diào)用pthread_mutex_lock函數(shù),函數(shù)立即返回錯(cuò)誤碼EDEADLK。
mutex的鎖定函數(shù)會(huì)阻塞,有時(shí)需要一個(gè)不阻塞就能知道m(xù)utex是否已鎖定的函數(shù),以在發(fā)現(xiàn)mutex已鎖的情況下去完成其他工作并在以后再來檢查。這樣的函數(shù)是: pthread_mutex_trylock 如果傳入的mutex已經(jīng)被其他線程鎖定,那么這個(gè)函數(shù)返回錯(cuò)誤碼EBUSY;如果未被鎖定,此函數(shù)會(huì)鎖定mutex,并返回0。這個(gè)函數(shù)不會(huì)阻塞。
|