再探 同步與互斥

      網友投稿 758 2025-03-31

      @[toc]

      線程

      先講線程吧,目前我能接觸到的應用場景還沒有達到能夠讓進程之間出現互斥的狀況。

      鎖種

      解決互斥目前最常用的操作就是上鎖了吧,來看看有多少鎖。

      不是什么時候都要靠上鎖的。從根源出發,我們為什么需要上鎖?因為線程在使用資源的過程中可能會出現沖突,對于這種會出現沖突的資源,還是鎖住輪著用比較好。

      但是有的資源其實很小,如果要在業務層面一鎖一解鎖也麻煩,于是就有了內核擔保的原子變量進行原子操作。

      #include #include #include #include //其中包含很多原子操作 #include using namespace std; volatile atomic_bool isReady = false; //volatile:防止共享變量被緩存,導致線程跑來跑去 volatile atomic_int mycount = 0; void task() { while (!isReady) { this_thread::yield(); //出讓時間片,等待下一次調用 } for (int i = 0; i < 100; i++) { mycount++; } } int main() { vector tvec; for (int i = 0; i < 10;i++) { tvec.push_back(thread(task)); } this_thread::sleep_for(chrono::seconds(3)); isReady = true; for (thread& t : tvec) { t.join(); } cout << mycount << endl; return 0; }

      看來下一篇要學習整理一下這些C++11新技術了,挺有意思的。

      樂觀鎖是一種思想,具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,需要再次查看該字段的值是否和第一次的一樣。如果一樣更新,反之拒絕。之所以叫樂觀,因為這個模式沒有從數據庫加鎖。

      ==樂觀鎖比較適用于讀多寫少的情況(多讀場景),悲觀鎖比較適用于寫多讀少的情況(多寫場景)==。

      class optimistic_lock(){ public: static optimistic_lock& instance(){ static optimistic_lock op_lock; return op_lock; } int get_vision(){ return vision; } void update_vision(){ ++vision; } private: int vision; //版本號 mutex mutex_; optimistic_lock(){ vision = 0; } //設置單例 }

      外部操作時:

      //獲取數據,也獲取vision int vision = optimistic_lock.get_vision(); //數據回寫時 bool update_data(){ if(vision == optimistic_lock.get_vision()){ mutex_.lock(); //想來想去,這里還是要上個鎖 //如果不上鎖,兩個線程都還沒寫入,且都進入了這層判斷,那豈不是都可以給vision加上1了 //但是如果這里上了鎖,這個樂觀鎖還有什么樂觀的意義? //曉得了,只要不寫,也沒什么 if(vision == this->vision){ //雙保險,參考懶漢模式的線程安全 //這里更新數據 optimistic_lock.update_vision(); mutex_.unlock(); return true; } else{ return false; } } else{ return false; } };

      悲觀鎖是一種悲觀思想,它總認為最壞的情況可能會出現,它認為數據很可能會被其他人所修改,所以悲觀鎖在持有數據的時候總會把資源 或者 數據 鎖住,這樣其他線程想要請求這個資源的時候就會阻塞,直到等到悲觀鎖把資源釋放為止。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。悲觀鎖的實現往往依靠數據庫本身的鎖功能實現。

      關于數據庫里面的鎖過兩天寫數據庫的時候會安排上。

      在樂觀鎖與悲觀鎖的選擇上面,主要看下兩者的區別以及適用場景就可以了。

      1??響應效率:如果需要非常高的響應速度,建議采用樂觀鎖方案,成功就執行,不成功就失敗,不需要等待其他并發去釋放鎖。樂觀鎖并未真正加鎖,效率高。一旦鎖的粒度掌握不好,更新失敗的概率就會比較高,容易發生業務失敗。

      2??沖突頻率:如果沖突頻率非常高,建議采用悲觀鎖,保證成功率。沖突頻率大,選擇樂觀鎖會需要多次重試才能成功,代價比較大。

      3??重試代價:如果重試代價大,建議采用悲觀鎖。悲觀鎖依賴數據庫鎖,效率低。更新失敗的概率比較低。

      自旋鎖和互斥鎖嘛,一直在用的,不過以前只是簡單的叫它們:鎖。原來人家有名字的啊。

      wait() 曉得不?timewait()曉得不?

      互斥鎖:阻塞等待

      自旋鎖:等兩下就去問一聲:好了不?我很急啊!好了不?你快點啊。。。哈哈哈哈哈

      自旋鎖的原理比較簡單,如果持有鎖的線程能在短時間內釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞狀態,它們只需要等一等(自旋),等到持有鎖的線程釋放鎖之后即可獲取,這樣就==避免了用戶進程和內核切換的消耗==。

      因為自旋鎖避免了操作系統進程調度和線程切換,所以自旋鎖通常==適用在時間比較短的情況下==。由于這個原因,操作系統的內核經常使用自旋鎖。但是,==如果長時間上鎖的話,自旋鎖會非常耗費性能,它阻止了其他線程的運行和調度==。線程持有鎖的時間越長,則持有該鎖的線程將被 OS(Operating System) 調度程序中斷的風險越大。如果發生中斷情況,那么其他線程將保持旋轉狀態(反復嘗試獲取鎖),而持有該鎖的線程并不打算釋放鎖,這樣導致的是結果是無限期推遲,直到持有鎖的線程可以完成并釋放它為止。

      解決上面這種情況一個很好的方式是給自旋鎖設定一個自旋時間,等時間一到立即釋放自旋鎖。適應性自旋鎖意味著自旋時間不是固定的了,而是由前一次在同一個鎖上的自旋時間以及鎖擁有的狀態來決定,基本認為一個線程上下文切換的時間是最佳的一個時間。

      概念性的東西就不多說了吧,基本就是人道主義出發設計的鎖,稍微學過都都能想到這種鎖。

      互斥變量用pthread_mutex_t數據類型表示,在使用互斥變量以前,必須首先對它進行初始化,可以把它置為常量PTHREAD_MUTEX_INITIALIZER(只對靜態分配的互斥量),也可以通過調用pthread_mutex_init函數進行初始化。==如果動態地分配互斥量(例如通過調用malloc函數),那么在釋放內存前需要調用pthread_mutex_destroy==。

      #include int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex);

      返回值:若成功則返回0,否則返回錯誤編號

      要用默認的屬性初始化互斥量,只需把attr設置為NULL。

      對互斥量進行加鎖,需要調用pthread_mutex_lock,如果互斥量已經上鎖,調用線程將阻塞直到互斥量被解鎖。對互斥量解鎖,需要調用pthread_mutex_unlock。

      #include int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);

      返回值:若成功則返回0,否則返回錯誤編號

      如果線程不希望被阻塞,它可以使用pthread_mutex_trylock嘗試對互斥量進行加鎖。如果調用pthread_mutex_trylock時互斥量處于未鎖住狀態,那么pthread_mutex_trylock將鎖住互斥量,不會出現阻塞并返回0,否則pthread_mutex_trylock就會失敗,不能鎖住互斥量,而返回EBUSY。

      讀寫鎖,我個人認為是在樂觀鎖的思想上進行了升級,并實例化出來了。

      讀寫鎖可以有三種狀態:讀模式下加鎖狀態,寫模式下加鎖狀態,不加鎖狀態。一次只有一個線程可以占有寫模式的讀寫鎖,但是==多個線程可以同時占有讀模式的讀寫鎖==。

      當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是如果線程希望以==寫模式==對此鎖進行加鎖,==它必須阻塞直到所有的線程釋放讀鎖==。雖然讀寫鎖的實現各不相同,但當讀寫鎖處于讀模式鎖住狀態時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會==阻塞隨后的讀模式鎖請求==。這樣可以避免讀模式鎖長期占用,而等待的寫模式鎖請求一直得不到滿足。

      讀寫鎖非常適合于對數據結構讀的次數遠大于寫的情況。當讀寫鎖在寫模式下時,它所保護的數據結構就可以被安全地修改,因為當前只有一個線程可以在寫模式下擁有這個鎖。當讀寫鎖在讀模式下時,只要線程獲取了讀模式下的讀寫鎖,該鎖所保護的數據結構可以被多個獲得讀模式鎖的線程讀取。

      class RDWR_lock{ public: static RDWR_lock& instance(){ static RDWR_lock rdwr_lock; return rdwr_lock; } void WR_lock(){ while(lock_flag != WR_ONLY){} //如果被加了寫鎖,那就阻塞在這邊等著吧 ++write_count; } void RD_lock(){ if(lock_flag == RD_ONLY){ mutex_.lock(); //可以去開始寫了,寫完記得解鎖 } else if(lock_flag == WR_ONLY){ //說明它是第一個進來等的 mutex_.lock(); lock_flag = RD_WAIT; while(write_count){} //擱這兒等著 lock_flag = RD_ONLY; //可以開始寫了 //寫完狀態換回去,可以解鎖了 } else{ mutex_.lock(); //等著拿鎖 //回去寫吧 } } void unlock(){ switch lock_flag{ case WR_ONLY: --write_count; case RD_ONLY: lock_flag = WR_ONLY; mutex_.unlock(); default: LOG_FATAL("unlock RDLR_lock failed in thread %p",getpid()); } } private: RDWR_lock(){ write_count = 0; lock_flag = WR_ONLY; } int write_count; //加讀鎖數量 enum flag = { WR_ONLY = 1, //只讀 RD_WAIT, //等待寫鎖 RD_ONLY //只寫 } int lock_flag; mutex mutex_; };

      與互斥量一樣,讀寫鎖在使用之前必須初始化,在釋放它們底層的內存前必須銷毀。

      #include int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 兩者的返回值都是:若成功則返回0,否則返回錯誤編號

      在釋放讀寫鎖占用的內存之前,需要調用pthread_rwlock_destroy做清理工作。如果pthread_rwlock_init為讀寫鎖分配了資源,pthread_rwlock_destroy將釋放這些資源。如果在調用pthread_rwlock_destroy之前就釋放了讀寫鎖占用的內存空間,那么分配給這個鎖的資源就丟失了。

      要在讀模式下鎖定讀寫鎖,需要調用pthread_rwlock_rdlock;要在寫模式下鎖定讀寫鎖,需要調用pthread_rwlock_wrlock。不管以何種方式鎖住讀寫鎖,都可以調用pthread_rwlock_unlock進行解鎖。

      #include int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 所有的返回值都是:若成功則返回0,否則返回錯誤編號

      在實現讀寫鎖的時候可能會對共享模式下可獲取的鎖的數量進行限制,所以需要檢查pthread_rwlock_rdlock的返回值。即使pthread_rwlock_wrlock和pthread_rwlock_unlock有錯誤的返回值,如果鎖設計合理的話,也不需要檢查其返回值。錯誤返回值的定義只是針對不正確地使用讀寫鎖的情況,例如未經初始化的鎖,或者試圖獲取已擁有的鎖從而可能產生死鎖這樣的錯誤返回等。

      提一嘴,讀寫鎖也有那個時間機制的。

      在講死鎖之前先了解一個新的函數:

      pthread_mutex_timedlock函數(第三版新增)

      當請求一個已經加鎖的互斥量時,如果我們想要限定線程阻塞的時間(時間到了就不再阻塞等待),這時需要使用pthread_mutex_timedlock函數。pthread_mutex_timedlock函數類似于pthread_mutex_lock,只不過一旦設置的超時值到達,pthread_mutex_timedlock函數會返回錯誤代碼ETIMEDOUT,線程不再阻塞等待。

      #include #include int pthread_mutex_timedlock( pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr );

      返回值:若成功則返回0,失敗則返回錯誤代碼

      超時值指定了我們要等待的時間,它使用絕對時間(而不是相對時間:我們指定線程將一直阻塞等待直到時刻X,而不是說我們將要阻塞X秒鐘。)。該時間值用timespec結構表示:秒和納秒。

      不然我還真不知道要怎么手動破開一個死鎖狀態。

      就有時候吧,不是咱想死鎖的。

      在多道程序系統中,若對資源的管理、分配和使用不當,也會產生一種危險,即在一定條件下會導致系統發生一種隨機性錯誤——死鎖。

      多個進程所共享的資源不足,引起它們對資源的競爭而產生死鎖

      -競爭可剝奪和非剝奪性資源

      -競爭非剝奪性資源

      進程運行過程中,請求和釋放資源的順序不當,而導致進程死鎖

      -進程推進順序合法

      -進程推進順序非法

      再有就是我們自己忘記釋放鎖了,這個是我們可以操控的。

      預防死鎖

      通過設置某些限制條件,以破壞產生死鎖的四個必要條件中的一個或幾個,來防止發生死鎖。

      避免死鎖

      在資源的動態分配過程中,使用某種方法去防止系統進入不安全狀態,從而避免了死鎖的發生。

      檢測死鎖

      檢測死鎖方法允許系統運行過程中發生死鎖。但通過系統所設置的檢測機構,可以及時檢測出死鎖的發生,并精確地確定與死鎖有關的進程和資源,然后采取適當措施,從系統中消除所發生的死鎖

      解除死鎖

      解除死鎖是與檢測死鎖相配套的一種設施,用于將進程從死鎖狀態下解脫出來

      屬于C++11新特性,這里先提一嘴,回頭專門寫一篇C++11新特性的。

      lock_guard是一個互斥量包裝程序,它提供了一種方便的RAII(Resource acquisition is initialization )風格的機制來在作用域塊的持續時間內擁有一個互斥量。

      創建lock_guard對象時,它將嘗試獲取提供給它的互斥鎖的所有權。當控制流離開lock_guard對象的作用域時,lock_guard析構并釋放互斥量。

      它的特點如下:

      創建即加鎖,作用域結束自動析構并解鎖,無需手工解鎖 不能中途解鎖,必須等作用域結束才解鎖 不能復制

      #include #include #include int g_i = 0; std::mutex g_i_mutex; // protects g_i void safe_increment() { const std::lock_guard lock(g_i_mutex); ++g_i; std::cout << std::this_thread::get_id() << ": " << g_i << '\n'; // g_i_mutex is automatically released when lock // goes out of scope } int main() { std::cout << "main: " << g_i << '\n'; std::thread t1(safe_increment); std::thread t2(safe_increment); t1.join(); t2.join(); std::cout << "main: " << g_i << '\n'; }

      要玩的轉這個,那真的要了解C++11新特性了,因為這里面涉及了智能指針和綁定器。

      unique_lock是一個通用的互斥量鎖定包裝器,它允許延遲鎖定,限時深度鎖定,遞歸鎖定,鎖定所有權的轉移以及與條件變量一起使用。

      簡單地講,unique_lock 是 lock_guard 的升級加強版,它具有 lock_guard 的所有功能,同時又具有其他很多方法,使用起來更強靈活方便,能夠應對更復雜的鎖定需要。

      特點如下:

      創建時可以不鎖定(通過指定第二個參數為std::defer_lock),而在需要時再鎖定 可以隨時加鎖解鎖 作用域規則同 lock_grard,析構時自動釋放鎖 不可復制,可移動 條件變量需要該類型的鎖作為參數(此時必須使用unique_lock)

      #include #include #include struct Box { explicit Box(int num) : num_things{num} {} int num_things; std::mutex m; }; void transfer(Box &from, Box &to, int num) { // don't actually take the locks yet std::unique_lock lock1(from.m, std::defer_lock); std::unique_lock lock2(to.m, std::defer_lock); // lock both unique_locks without deadlock std::lock(lock1, lock2); from.num_things -= num; to.num_things += num; // 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors } int main() { Box acc1(100); Box acc2(50); std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10); std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5); t1.join(); t2.join(); }

      條件變量

      條件變量提供了另一種同步的方式。互斥量通過控制對數據的訪問實現了同步,而條件變量允許根據實際的數據值來實現同步。

      沒有條件變量,程序員就必須使用線程去輪詢(可能在臨界區),查看條件是否滿足。這樣比較消耗資源,因為線程連續繁忙工作。條件變量是一種可以實現這種輪詢的方式。

      條件變量往往和互斥一起使用

      使用條件變量的代表性順序如下:

      條件變量原語

      //初始化條件變量: //本人還是喜歡靜態初始化,省事兒 pthread_cont_t cont = PTHREAD_COND_INITIALIZER; //好,再看看動態初始化 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); //參數釋義:cond:用于接收初始化成功管道條件變量 //attr:通常為NULL,且被忽略 //有初始化那肯定得有銷毀 int pthread_cond_destroy(pthread_cond_t *cond); //既然說條件變量是用來等待的,那就更要看看這等待的特殊之處了 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); //無條件等待 int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t mytex,const struct timespec *abstime); //計時等待 //好,加入等待喚醒大軍了,那得看看怎么去喚醒了 int pthread_cond_signal(pthread_cond_t *cptr); //喚醒一個等待該條件的線程。存在多個線程是按照其隊列入隊順序喚醒其中一個 int pthread_cond_broadcast(pthread_cond_t * cptr); //廣播,喚醒所喲與等待線程

      條件變量與互斥鎖

      在服務器編程中常用的線程池,多個線程會操作同一個任務隊列,一旦發現任務隊列中有新的任務,子線程將取出任務;這里因為是多線程操作,必然會涉及到用互斥鎖保護任務隊列的情況(否則其中一個線程操作了任務隊列,取出線程到一半時,線程切換又取出相同任務)。但是互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。設想,每個線程為了獲取新的任務不斷得進行這樣的操作:鎖定任務隊列,檢查任務隊列是否有新的任務,取得新的任務(有新的任務)或不做任何操作(無新的任務),釋放鎖,這將是很消耗資源的。

      而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起配合使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發生變化。一旦其他的某個線程改變了條件變量,他將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線程間的同步。對應于線程池的場景,我們可以讓線程處于等待狀態,當主線程將新的任務放入工作隊列時,發出通知(其中一個或多個),得到通知的線程重新獲得鎖,取得任務,執行相關操作。

      (1)必須在互斥鎖的保護下喚醒,否則喚醒可能發生在鎖定條件變量之前,照成死鎖。

      (2)喚醒阻塞在條件變量上的所有線程的順序由調度策略決定

      (3)如果沒有線程被阻塞在調度隊列上,那么喚醒將沒有作用。

      (4)以前不懂事兒,就喜歡廣播。由于pthread_cond_broadcast函數喚醒所有阻塞在某個條件變量上的線程,這些線程被喚醒后將再次競爭相應的互斥鎖,所以必須小心使用pthread_cond_broadcast函數。

      虛假喚醒與喚醒丟失

      在多核處理器下,pthread_cond_signal可能會激活多于一個線程(阻塞在條件變量上的線程)。結果是,當一個線程調用pthread_cond_signal()后,多個調用pthread_cond_wait()或pthread_cond_timedwait()的線程返回。這種效應成為”虛假喚醒”(spurious wakeup)

      Linux幫助里面有

      為什么不去修正,性價比不高嘛。

      所以通常的標準解決辦法是這樣的(妙!):

      無論哪種等待方式,都必須和一個互斥量配合,以防止多個線程來打擾。

      互斥鎖必須是普通鎖或適應鎖,并且在進入pthread_cond_wait之前必須由本線程加鎖。

      在更新等待隊列前,mutex必須保持鎖定狀態. 在線程進入掛起,進入等待前,解鎖。(好繞啊,我已經盡力斷句了)

      在條件滿足并離開pthread_cond_wait前,上鎖。以恢復它進入cont_wait之前的狀態。

      為什么等待會被上鎖?

      以免出現喚醒丟失問題。 這里有個大神解釋要不要看:https://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex 做事做全套,源碼也給放這兒了:https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html

      在放些咱能看懂的中文解釋:將線程加入喚醒隊列后方可解鎖。保證了線程在陷入wait后至被加入喚醒隊列這段時間內是原子的。

      但這種原子性依賴一個前提條件:喚醒者在調用pthread_cond_broadcast或pthread_cond_signal喚醒等待者之前也必須對相同的mutex加鎖。

      滿足上述條件后,如果一個等待事件A發生在喚醒事件B之前,那么A也同樣在B之前獲得了mutex,那A在被加入喚醒隊列之前B都無法進入喚醒調用,因此保證了B一定能夠喚醒A;試想,如果A、B之間沒有mutex來同步,雖然B在A之后發生,但是可能B喚醒時A尚未被加入到喚醒隊列,這便是所謂的喚醒丟失。

      在線程未獲得相應的互斥鎖時調用pthread_cond_signal或pthread_cond_broadcast函數可能會引起喚醒丟失問題。 喚醒丟失往往會在下面的情況下發生: 一個線程調用pthread_cond_signal或pthread_cond_broadcast函數; 另一個線程正處在測試條件變量和調用pthread_cond_wait函數之間; 沒有線程正在處在阻塞等待的狀態下。

      使用條件變量

      //例子演示了使用Pthreads條件變量的幾個函數。主程序創建了三個線程,兩個線程工作,根系“count”變量。第三個線程等待count變量值達到指定的值。 #include #include #define NUM_THREADS 3 #define TCOUNT 10 #define COUNT_LIMIT 12 int count = 0; int thread_ids[3] = {0,1,2}; pthread_mutex_t count_mutex; pthread_cond_t count_threshold_cv; void *inc_count(void *idp) { int j,i; double result=0.0; int *my_id = idp; for(i=0; i

      再探 同步與互斥

      信號量

      咳咳,先來幾個大神的意見:

      我的建議是不要使用 semaphore。https://www.zhihu.com/question/47411729 陳碩

      陳碩大佬的話一般都這么簡短,下面有一位大神體諒我們比較菜,給出了釋義:

      最近你的回答都要簡略啦!我來幫你補充一下。1)雖然,從邏輯上可以基于信號量來實現任何鎖,但信號量并不是一個“好用”的東西。2)計算機實現的最小粒度的同步機制并不是信號量,而是spinlock(自旋鎖)一類的東西,因此,與其說spinlock可以基于信號量來實現,不如說,信號量本身就是靠spinlock實現的。3)信號量有很多種實現,但都繞不開操作系統層面的支持,這樣一來,信號量的開銷就遠高于spinlock這樣的cpu原生實現的鎖。4)在多核環境下,用信號量來實現數據同步可能會造成一些問題,需要編程者掌握較高的并發編程知識才能避免。(涉及到CPU亂序執行、內存亂序讀取等問題)5)以Java舉例,Java的sync實質上是鎖+內存屏障(用來避免亂序執行和亂序讀取),因此,不理解CPU內存模型的初學者容易認為sync僅僅只是一個鎖,用信號量就能模擬。6)綜上所述,信號量只能模擬鎖,但不能模擬同步機制,同步機制需要鎖+內存屏障,現成的鎖往往自帶內存屏障,所以內存屏障對于編程者而言是透明的,而許多編程者不知道這一點,試圖用信號量模擬鎖,這樣一來程序就會fail。

      一個信號量 S 是個整型變量,它除了初始化外只能通過兩個標準原子操作:wait () 和 signal() 來訪問:

      操作 wait() 最初稱為 P(荷蘭語proberen,測試); 操作 signal() 最初稱為 V(荷蘭語verhogen,增加)

      對信號量有4種操作(#include): 1. 初始化(initialize),也叫做建立(create) int sem_init(sem_t *sem, int pshared, unsigned int value); 2. 等信號(wait),也可叫做掛起(suspend)int sem_wait(sem_t *sem); 3. 給信號(signal)或發信號(post) int sem_post(sem_t *sem); 4.清理(destroy) int sem_destory(sem_t *sem);

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:wps加點字的點怎么加(在wps中怎么給字加點)
      下一篇:在wps表格中如何快速錄入性別(wps怎么自動生成性別)
      相關文章
      亚洲一区二区无码偷拍| 亚洲视频在线视频| 亚洲娇小性xxxx色| 亚洲一级大黄大色毛片| 亚洲国产精品久久丫 | 亚洲bt加勒比一区二区| 日韩精品亚洲aⅴ在线影院| 久久久亚洲精品蜜桃臀 | 亚洲日产2021三区| 亚洲理论片在线中文字幕| 亚洲第一页在线视频| 亚洲欧洲日产国码在线观看| 亚洲国产成人精品无码区在线网站| 亚洲网址在线观看| 亚洲手机中文字幕| 精品久久亚洲中文无码| 亚洲kkk4444在线观看| 亚洲精品蜜夜内射| 亚洲第一网站男人都懂| 亚洲人成影院在线无码观看| 国产AV无码专区亚洲AV漫画 | 亚洲jjzzjjzz在线播放| 亚洲真人无码永久在线观看| 亚洲AV无码专区亚洲AV桃| 国产成人不卡亚洲精品91| 亚洲精品人成无码中文毛片| 国产亚洲精品无码拍拍拍色欲| 亚洲码国产精品高潮在线| 亚洲AV日韩AV高潮无码专区| 久久亚洲AV成人出白浆无码国产| 亚洲福利电影一区二区?| 亚洲宅男精品一区在线观看| 亚洲欧美日韩中文字幕在线一区 | 亚洲av日韩综合一区久热| 亚洲AV无码不卡在线观看下载| 国产亚洲精aa成人网站| 亚洲VA中文字幕无码一二三区| 久久精品国产亚洲av日韩| 亚洲一区二区免费视频| 亚洲高清毛片一区二区| 亚洲人成影院在线无码观看|