电竞比分网-中国电竞赛事及体育赛事平台

分享

線程同步與互斥鎖

 印度阿三17 2019-05-31

相比多進(jìn)程模型,多線程模型最大的優(yōu)勢(shì)在于數(shù)據(jù)共享非常方便,同一進(jìn)程內(nèi)的多個(gè)線程可以使用相同的地址值訪問(wèn)同一塊內(nèi)存數(shù)據(jù)。但是,當(dāng)多個(gè)線程對(duì)同一塊內(nèi)存數(shù)據(jù)執(zhí)行“讀?處理?更新”操作時(shí),會(huì)由于線程的交叉執(zhí)行而造成數(shù)據(jù)的錯(cuò)誤。

例如以下代碼段,當(dāng) thread_func() 同時(shí)在多個(gè)線程中執(zhí)行時(shí),更新到 glob_value 中的值就會(huì)互相干擾,產(chǎn)生錯(cuò)誤結(jié)果。

#define LOOP_COUNT   1000000
int glob_value = 0;

void * thread_func(void * args)
{
    int counter = 0;
    while(counter   < LOOP_COUNT)
    {
        int local = glob_value;
        local  ;
        glob_value = local;
    }
}

解決這類問(wèn)題的關(guān)鍵在于,當(dāng)一個(gè)線程正在執(zhí)行“讀?處理?更新”操作時(shí),保證其他線程不會(huì)中途闖入與其交叉執(zhí)行。不可被打斷的執(zhí)行序列稱為臨界區(qū),保證多個(gè)線程不會(huì)交叉執(zhí)行同一臨界區(qū)的技術(shù)稱為線程同步。

1 互斥鎖的使用

最常用的線程同步技術(shù)是互斥鎖,Linux 線程庫(kù)中的相關(guān)函數(shù)有:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

這里pthread的p代表POSIX線程

所有線程都有一個(gè)線程號(hào),也就是Thread ID。其類型為pthread_t。通過(guò)調(diào)用pthread_self()函數(shù)可以獲得自身的線程號(hào)。

pthread_mutex_lock() 負(fù)責(zé)在進(jìn)入臨界區(qū)之前對(duì)臨界區(qū)加鎖;
pthread_mutex_unlock() 負(fù)責(zé)在執(zhí)行完臨界區(qū)處理時(shí)給臨界區(qū)解鎖。

當(dāng)某個(gè)線程試圖給一個(gè)已經(jīng)處在加鎖狀態(tài)的臨界區(qū)再次加鎖時(shí),該線程就會(huì)被臨時(shí)掛起,一直等到該臨界區(qū)被解鎖后,才會(huì)被喚醒并繼續(xù)執(zhí)行。

如果同時(shí)有多個(gè)線程等待某個(gè)臨界區(qū)解鎖,那下次被喚醒的進(jìn)程取決于內(nèi)核的調(diào)度策略,并沒(méi)有固定的順序。

靜態(tài)分配的 mutex 變量在使用之前應(yīng)該被初始化為 PTHREAD_MUTEX_INITIALIZER,而動(dòng)態(tài)分配的 mutex 需要調(diào)用 pthread_mutex_init() 進(jìn)行初始化,且只被某個(gè)線程初始化一次,可以利用 pthread_once() 函數(shù)方便完成。

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

多個(gè)線程在臨界區(qū)上的執(zhí)行是串行的,開(kāi)發(fā)者應(yīng)該盡量減少程序在臨界區(qū)內(nèi)的停留時(shí)間,以提高程序的并行性。因此,臨界區(qū)不應(yīng)該包含任何非必須的邏輯,以及任何可能帶來(lái)高延遲的 IO 等操作

2 互斥鎖的保護(hù)范圍和使用順序

對(duì)互斥鎖加鎖的不恰當(dāng)使用會(huì)造成線程的死鎖,比如下面這兩種情況。

  1. 典型的情況是,兩個(gè)線程執(zhí)行時(shí)都需要鎖定互斥鎖 A 和 B,在一個(gè)線程中,鎖定順序是先鎖定 A,后鎖定 B,而另一個(gè)線程的鎖定順序是先鎖定 B,再鎖定 A。這種情況下,當(dāng)一個(gè)線程已經(jīng)鎖定了 A 而另一個(gè)線程恰好鎖定了 B 時(shí),雙方因互相爭(zhēng)用對(duì)方已鎖定的互斥鎖,誰(shuí)也不讓步,而陷入死鎖狀態(tài)。
  1. 另一種情況是,一個(gè)線程已經(jīng)鎖定了互斥鎖 A,但在其后的處理邏輯中試圖再次鎖定 A,這時(shí)該線程會(huì)讓自己陷入睡眠狀態(tài),再也等不到被喚醒的時(shí)候。

因此,開(kāi)發(fā)者需要仔細(xì)規(guī)劃互斥鎖保護(hù)范圍和使用順序

3 避免死鎖的兩個(gè)加鎖函數(shù)

為了避免出現(xiàn)死鎖問(wèn)題,可以使用另外兩種變體的鎖定函數(shù),如下所示:

int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

前者可以在鎖定失敗后立即返回,后者可以在一段超時(shí)時(shí)間后返回,

應(yīng)用這兩個(gè)函數(shù)可以處理這種錯(cuò)誤情況,而避免陷入無(wú)限的死鎖中。

在 Linux 中,實(shí)現(xiàn)互斥鎖采用的是 Futex(Fast Userspace Mutex)方案。在該實(shí)現(xiàn)中,只有發(fā)生了鎖的爭(zhēng)用才需要陷入到內(nèi)核空間中處理,否則所有的操作都可以在用戶空間內(nèi)快速完成。在大多數(shù)情況下,互斥鎖本身的效率很高,其平均開(kāi)銷大約相當(dāng)于幾十次內(nèi)存讀寫(xiě)和算數(shù)運(yùn)算所花費(fèi)的時(shí)間。

來(lái)源:http://www./content-4-220351.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多