【Linux】多線程(自旋鎖、讀寫鎖)

自旋鎖概述

自旋鎖是一種線程同步機制,旨在保護共享資源免受并發(fā)訪問的影響。在多個線程嘗試獲取鎖時,它們會持續(xù)在循環(huán)中自旋(即不斷檢查鎖是否可用),而不是立即進入休眠狀態(tài)等待鎖的釋放。這種方法減少了線程切換的開銷,適合于短時間內(nèi)鎖的競爭情況。然而,不恰當(dāng)?shù)氖褂每赡軙?dǎo)致cpu資源的浪費。

自旋鎖的原理

自旋鎖通常使用一個共享的標(biāo)志位(例如一個布爾值)來表示鎖的狀態(tài)。當(dāng)標(biāo)志位為true時,表示鎖已被某個線程占用;當(dāng)標(biāo)志位為false時,表示鎖可用。當(dāng)一個線程嘗試獲取自旋鎖時,它會不斷檢查標(biāo)志位:

  • 如果標(biāo)志位為false,表示鎖可用,線程將設(shè)置標(biāo)志位為true,表示自己占用了鎖,并進入臨界區(qū)。
  • 如果標(biāo)志位為true(即鎖已被其他線程占用),線程會在一個循環(huán)中持續(xù)自旋等待,直到鎖被釋放。

自旋鎖的優(yōu)點與缺點

優(yōu)點:

  • 低延遲:自旋鎖適用于短時間內(nèi)的鎖競爭情況,因為它不會讓線程進入休眠狀態(tài),從而避免了線程切換的開銷,提高了鎖操作的效率。
  • 減少系統(tǒng)調(diào)度開銷:等待鎖的線程不會被阻塞,不需要上下文切換,從而減少了系統(tǒng)調(diào)度的開銷。

缺點:

  • CPU資源浪費:如果鎖的持有時間較長,等待獲取鎖的線程會一直循環(huán)等待,導(dǎo)致CPU資源的浪費。
  • 可能引起活鎖:當(dāng)多個線程同時等待一個鎖時,如果沒有適當(dāng)?shù)耐吮懿呗裕赡軙?dǎo)致所有線程都在不斷檢查鎖狀態(tài)而無法進入臨界區(qū),形成活鎖。

自旋鎖的使用場景

  • 短暫等待的情況:適用于鎖被占用時間非常短的場景,如多線程對共享數(shù)據(jù)進行簡單的讀寫操作。
  • 多線程鎖使用:通常用于系統(tǒng)底層,同步多個CPU對共享資源的訪問。

linux提供的自旋鎖系統(tǒng)調(diào)用

int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock); int pthread_spin_init(pthread_spinlock_t *lock, int pshared); int pthread_spin_destroy(pthread_spinlock_t *lock);
  • pshared有兩個選項,PTHREAD_PROCESS_private和PTHREAD_PROCESS_SHARED。private選項表示自旋鎖只能在同一進程內(nèi)的多個線程內(nèi)使用,pshared表示可以在多個不同的進程內(nèi)使用同一個自旋鎖。

自旋鎖的注意事項

  • 在使用自旋鎖時,需要確保鎖被釋放的時間盡可能短,以避免CPU資源的浪費。
  • 在多CPU環(huán)境下,自旋鎖可能不如其他鎖機制高效,因為它可能導(dǎo)致線程在不同的CPU上自旋等待。

自旋鎖的結(jié)論

自旋鎖是一種適用于短時間內(nèi)鎖競爭情況的同步機制,它通過減少線程切換的開銷來提高鎖操作的效率。然而,它也存在CPU資源浪費和可能引起活鎖等缺點。在使用自旋鎖時,需要根據(jù)具體的應(yīng)用場景進行選擇,并確保鎖被釋放的時間盡可能短。

樣例代碼

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h>  int ticket = 1000; pthread_spinlock_t lock;  void *route(void *arg) {     char *id = (char *)arg;     while (1) {         pthread_spin_lock(&lock);         if (ticket > 0) {             usleep(1000);             printf("%s sells ticket:%dn", id, ticket);             ticket--;             pthread_spin_unlock(&lock);         } else {             pthread_spin_unlock(&lock);             break;         }     }     return NULL; }  int main(void) {     pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);     pthread_t t1, t2, t3, t4;     pthread_create(&t1, NULL, route, (void *)"thread 1");     pthread_create(&t2, NULL, route, (void *)"thread 2");     pthread_create(&t3, NULL, route, (void *)"thread 3");     pthread_create(&t4, NULL, route, (void *)"thread 4");     pthread_join(t1, NULL);     pthread_join(t2, NULL);     pthread_join(t3, NULL);     pthread_join(t4, NULL);     pthread_spin_destroy(&lock);     return 0; }

讀寫鎖概述

在編寫多線程程序時,常見一種情況是公共數(shù)據(jù)的修改機會較少,而讀取的機會則相對較多。通常,讀取過程伴隨著查找操作,耗時較長。如果對這種代碼段加鎖,會極大地降低程序的效率。針對這種多讀少寫的情況,有一種專門的處理方法,即讀寫鎖。

【Linux】多線程(自旋鎖、讀寫鎖)

pthread庫為我們提供了讀寫鎖。

讀寫鎖的初始化和銷毀

【Linux】多線程(自旋鎖、讀寫鎖)

讀寫鎖的加鎖和解鎖

【Linux】多線程(自旋鎖、讀寫鎖)【Linux】多線程(自旋鎖、讀寫鎖)【Linux】多線程(自旋鎖、讀寫鎖)

樣例代碼

#include <iostream> #include <pthread.h> #include <unistd.h> #include <vector> #include <cstdlib> #include <ctime>  // 共享資源 int shared_data = 0;  // 讀寫鎖 pthread_rwlock_t rwlock;  // 讀者線程函數(shù) void *Reader(void *arg) {     int number = *(int *)arg;     while (true) {         pthread_rwlock_rdlock(&rwlock); // 讀者加鎖         std::cout << "Reader " << number << " is reading. Shared data: " << shared_data << std::endl;         pthread_rwlock_unlock(&rwlock); // 讀者解鎖         sleep(1); // 模擬讀取時間     }     return NULL; }  // 寫者線程函數(shù) void *Writer(void *arg) {     int number = *(int *)arg;     while (true) {         pthread_rwlock_wrlock(&rwlock); // 寫者加鎖         shared_data++;         std::cout << "Writer " << number << " is writing. Shared data: " << shared_data << std::endl;         pthread_rwlock_unlock(&rwlock); // 寫者解鎖         sleep(1); // 模擬寫入時間     }     return NULL; }  int main() {     pthread_rwlock_init(&rwlock, NULL);      std::vector<pthread_t> threads;     int num_readers = 5;     int num_writers = 2;      for (int i = 0; i < num_readers; i++) {         int *arg = new int(i);         pthread_t thread;         pthread_create(&thread, NULL, Reader, arg);         threads.push_back(thread);     }      for (int i = 0; i < num_writers; i++) {         int *arg = new int(i);         pthread_t thread;         pthread_create(&thread, NULL, Writer, arg);         threads.push_back(thread);     }      for (auto thread : threads) {         pthread_join(thread, NULL);     }      pthread_rwlock_destroy(&rwlock);     return 0; }

讀者優(yōu)先策略

在這種策略中,系統(tǒng)會盡可能多地允許多個讀者同時訪問資源(比如共享文件或數(shù)據(jù)),而不會優(yōu)先考慮寫者。這意味著當(dāng)有讀者正在讀取時,新到達的讀者會立即被允許進入讀取區(qū),而寫者則會被阻塞,直到所有讀者都離開讀取區(qū)。讀者優(yōu)先策略可能會導(dǎo)致寫者饑餓(即寫者長時間無法獲得寫入權(quán)限),特別是當(dāng)讀者頻繁到達時。

寫者優(yōu)先策略

在這種策略中,系統(tǒng)會優(yōu)先考慮寫者。當(dāng)寫者請求寫入權(quán)限時,系統(tǒng)會盡快地讓寫者進入寫入?yún)^(qū),即使此時有讀者正在讀取。這通常意味著一旦有寫者到達,所有后續(xù)的讀者都會被阻塞,直到寫者完成寫入并離開寫入?yún)^(qū)。寫者優(yōu)先策略可以減少寫者等待的時間,但可能會導(dǎo)致讀者饑餓(即讀者長時間無法獲得讀取權(quán)限),特別是當(dāng)寫者頻繁到達時。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊15 分享