文章目錄
- 1.執行緒互斥背景知識(臨界資源,互斥,原子性定義)
- 2.對臨界資源保護的重要性
- 原子性的解釋
- 3.臨界資源的保護(Linux互斥鎖pthread_mutex_t)
- 初始化互斥鎖(pthread_mutex_init(pthread.h))
- 互斥鎖的銷毀(pthread_mutex_destroy(pthread.h))
- 臨界資源加鎖與解鎖(pthread_mutex_lock/pthread_mutex_unlock)
- 4.鎖的原子性分析
- 加鎖與釋放鎖的原子性的實作
- 5.可重入與執行緒安全
- 6.死鎖
- 阻塞掛起(資源等待佇列)
- 死鎖的必要條件
1.執行緒互斥背景知識(臨界資源,互斥,原子性定義)
- 臨界資源:多執行流下共享的資源稱為臨界資源,每個執行緒內部訪問臨界資源的代碼稱為臨界區
- 互斥:為了保護臨界資源,在任何時刻只有一個執行流進入臨界區訪問臨界資源,
- 原子性:不會被任何調度打斷的操作,通常有兩態1.完成 2.未完成
2.對臨界資源保護的重要性
如果臨界資源不保護
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int cout=1000;
void*Del(void*meg)
{
int Num=(int)meg;
while(1)
{
if(cout>0)
{
usleep(10000);
cout--;
printf("pthread%d cout=%d\n",Num,cout);
}
else
{
break;
}
}
}
int main()
{
pthread_t tid[4];
for(int i=0;i<4;i++)
{
pthread_create(&tid[i],NULL,Del,(void*)i);
}
for(int i=0;i<4;i++)
{
pthread_join(tid[i],NULL);
}
return 0;
}

此時會出現將cout減為負數的情況,這顯然是不正確的
原子性的解釋
造成上述原因是cout- -這個操作不是原子性的 cout- -需要經過下面的步驟
1.將記憶體中的cout值讀取到cpu上
2.對cout值進行-1.
3.將cout的值寫回記憶體中
設cout值為100
當執行緒1剛把記憶體中的cout讀到cpu中,此時搞好進行執行緒切換,這個cout值會作為執行緒背景關系資訊被保留下來,執行緒1認為此時cout值為100
執行緒2切換回后,當執行緒2執行時間比較長時,執行緒2把cout讀到cpu中,cout- -執行完后,又將cout寫回到記憶體上,此時cout值變為99,
執行緒切換回執行緒1時,執行緒1的背景關系資訊加載到cpu中,但這時執行緒1認為cout為100,所以就會出現兩個執行緒操作的cout的值不一致的錯誤
3.臨界資源的保護(Linux互斥鎖pthread_mutex_t)
根據上面的分析,我們知道:
執行緒與執行緒之間必須有互斥的約束,一個執行緒在訪問臨界資源,此時其他執行緒不能再訪問臨界資源,在Linux中這種保護通過互斥鎖來實作,當一個執行緒申請到鎖,其他執行緒就會阻塞等待鎖的釋放,保護了臨界資源
初始化互斥鎖(pthread_mutex_init(pthread.h))

如上圖給出了兩種初始化pthread_mutex_t的方法,一種是pthread_mutex_init函式初始化
另一種是 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
引數解釋:
mutex:要初始化的互斥鎖
attr:互斥鎖的屬性,一般為NULL默認
回傳值:成功回傳0,失敗回傳錯誤碼
互斥鎖的銷毀(pthread_mutex_destroy(pthread.h))

引數解釋:只需要傳入要銷毀的互斥鎖即可
回傳值:成功回傳0,失敗回傳錯誤碼,
臨界資源加鎖與解鎖(pthread_mutex_lock/pthread_mutex_unlock)

注意:加鎖有損于性能,
eg:
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int cout=100000;
pthread_mutex_t lock;
void*Del(void*meg)
{
int Num=(int)meg;
while(1)
{
pthread_mutex_lock(&lock);//進入臨界資源時加鎖
if(cout>0)
{
usleep(1000);
cout--;
printf("pthread%d cout=%d\n",Num,cout);
pthread_mutex_unlock(&lock);//出臨界資源解鎖
}
else
{
//如果此時不需要--,此時也要釋放鎖
pthread_mutex_unlock(&lock);
break;
}
}
}
int main()
{
pthread_t tid[4];
pthread_mutex_init(&lock,NULL);
for(int i=0;i<4;i++)
{
pthread_create(&tid[i],NULL,Del,(void*)i);
}
for(int i=0;i<4;i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&lock);
return 0;
}

如圖:這樣cout就不存在負數的情況了,但這樣還存在問題,因為執行緒剛剛將鎖釋放,這個執行緒對于鎖的競爭比其他執行緒更強,所以可能存在一個執行緒使得cout減少到0,這時需要引入同步機制,讓每個執行緒都有機會使得cout-- ,
注意:
1.執行緒申請到鎖后在臨界區也可以進行執行緒切換,即便當前執行緒被切換其他執行緒也無法進入臨界區
4.鎖的原子性分析
如上代碼,每個執行緒的狀態只有兩種,
1.已經申請到鎖 2.已經將鎖釋放
鎖可以多個執行緒所看到,所以鎖首先是臨界資源,
鎖也需要被保護,所以申請鎖的程序就是原子性的
加鎖與釋放鎖的原子性的實作
大多數體系結構提供了exchange或swap指令,用來交換暫存器和記憶體資料,因為只有一條指令,所以這個程序是原子性的,
加鎖與解鎖流程圖:

5.可重入與執行緒安全
可重入函式:如果當在一個執行流下在執行這個函式,另一個執行流也執行這個函式,如果運行不會有問題,這時稱為這個函式為可重入函式,
執行緒安全:當在多執行緒并發執行代碼時不會出現不同的結果(常見為全域變數和靜態變數等),稱為執行緒安全
函式可重入則一定是執行緒安全的,執行緒安全不一定是可重入的,
一個討論的是函式,一個討論的是執行緒
6.死鎖
死鎖:執行緒因為編碼失誤導致申請的鎖沒有釋放而導致永久等待的情況,
eg:連續申請兩次鎖,
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int cout=1000;
pthread_mutex_t lock;
void*Del(void*meg)
{
int Num=(int)meg;
while(1)
{
pthread_mutex_lock(&lock);//進入臨界資源時加鎖
pthread_mutex_lock(&lock);//連續申請兩次鎖導致死鎖
if(cout>0)
{
usleep(1000);
cout--;
printf("pthread%d cout=%d\n",Num,cout);
pthread_mutex_unlock(&lock);//出臨界資源解鎖
}
else
{
//如果此時不需要--,此時也要釋放鎖
pthread_mutex_unlock(&lock);
break;
}
}
}
int main()
{
pthread_t tid[4];
pthread_mutex_init(&lock,NULL);
for(int i=0;i<4;i++)
{
pthread_create(&tid[i],NULL,Del,(void*)i);
}
for(int i=0;i<4;i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&lock);
return 0;
}

阻塞掛起(資源等待佇列)

死鎖的必要條件
- 互斥條件:執行緒A有執行緒B的鎖,執行緒B有執行緒A的鎖,
- 請求與保持條件:A執行緒擁有鎖但還申請鎖
- 不剝奪條件:A執行緒擁有鎖,A執行緒不會被剝奪
- 回圈等待條件:執行緒A有執行緒B的鎖,執行緒B有執行緒A的鎖,執行緒A申請A鎖,執行緒B申請B鎖導致死鎖,
解除死鎖只要破壞死鎖的4個必要條件即可
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/382802.html
標籤:其他
下一篇:網路——路由進階與安全
