引言
本文爲第十二篇,線程同步之自旋鎖,在上一篇文章介紹了互斥量,通過互斥量解決線程同步的問題。本文是另一個解決線程同步的方法—自旋鎖
自旋鎖
自旋鎖的工作原理跟互斥量的工作原理其實是一模一樣的,它也是在訪問臨界資源之前加一個鎖,完成之後再將鎖給釋放掉。但是互斥量和自旋鎖還是有一點區別的
- 自旋鎖也是一種多線程同步的變量
- 使用了自旋鎖的線程會反覆檢查鎖變量是否可用,如果不可用就會循環反覆的檢查
- 因此自旋鎖不會讓出CPU,是一種忙等待狀態
因此自旋鎖其實就是:死循環等待鎖被釋放
自旋鎖好處
- 自旋鎖避免了進程或線程上下文切換的開銷(如果這個鎖佔用的時間不是很長,這個代價還是很小的)
- 操作系統內部很多地方都是使用自旋鎖,而不是互斥量
- 自旋鎖不適合在單核CPU使用(因爲自旋鎖在等待的時候並不會釋放CPU,如果在單核CPU使用的話會引起其它的進程或線程無法執行)
自旋鎖和互斥量(鎖)的比較
- 自旋鎖是一種非阻塞鎖,也就是說,如果某線程需要獲取自旋鎖,但該鎖已經被其他線程佔用時,該線程不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取自旋鎖
- 互斥量是阻塞鎖,當某線程無法獲取互斥量時,該線程會被直接掛起,該線程不再消耗CPU時間,當其他線程釋放互斥量後,操作系統會激活那個被掛起的線程,讓其投入運行
兩種鎖適用的場景
- 如果是多核處理器,如果預計線程等待鎖的時間很短,短到比線程兩次上下文切換時間要少的情況下,使用自旋鎖是划算的
- 如果是多核處理器,如果預計線程等待鎖的時間較長,至少比兩次線程上下文切換的時間要長,建議使用互斥量
- 如果是單核處理器,一般建議不要使用自旋鎖。因爲,在同一時間只有一個線程是處在運行狀態,那如果運行線程發現無法獲取鎖,只能等待解鎖,但因爲自身不掛起,所以那個獲取到鎖的線程沒有辦法進入運行狀態,只能等到運行線程把操作系統分給它的時間片用完,纔能有機會被調度。這種情況下使用自旋鎖的代價很高
建議:如果加鎖的代碼經常被調用,但競爭情況很少發生時,應該優先考慮使用自旋鎖,自旋鎖的開銷比較小,互斥量的開銷較大
自旋鎖的代碼示例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
$include<vector>
//自旋鎖定義
pthread_spinlock_t spin_lock;
//臨界資源
int num=0;
//生產者
void *producer(void*){
int times = 100000000;//循環一百萬次
while(times--){
//加自旋鎖
pthread_spin_lock(&spin_lock);
num += 1;//每次生產一個產品
//解鎖
pthread_spin_unlock(&spin_lock);
}
}
//消費者
void *consumer(void*){
int times = 100000000;
while(times--){
//加自旋鎖
pthread_spin_lock(&spin_lock);
num -= 1;//每次消費一個產品
//解鎖
pthread_spin_unlock(&spin_lock);
}
}
int main()
{
printf("Start in main function.");
//初始化自旋鎖
pthread_spin_init(&spin_lock, 0);
//定義兩個線程
pthread_t thread1,thread2;
//一個執行生成者邏輯,一個執行消費者邏輯
pthread_create(&thread1, NULL, &producer, NULL);
pthread_create(&thread2, NULL, &consumer, NULL);
pthread_join(&thread1, NULL);
pthread_join(&thread2, NULL);
//打印臨界資源的值
printf("Print in main function: num = %d\n", num);
return 0;
}
執行結果
在快速變化的技術中尋找不變,纔是一個技術人的核心競爭力。知行合一,理論結合實踐