linux執行緒學習(3)
阿新 • • 發佈:2019-02-01
執行緒的同步
1. 互斥量
- 為什麼要使用互斥量:
當多個執行緒共享相同的記憶體時,需要每一個執行緒看到相同的檢視。當一個執行緒修改變數時,而其他執行緒也可以讀取或者修改這個變數,就需要對這些執行緒同步,確保他們不會訪問到無效的變數 - 互斥鎖的初始化和銷燬:
為了讓執行緒訪問資料不產生衝突,這要就需要對變數加鎖,使得同一時刻只有一個執行緒可以訪問變數。互斥量本質就是鎖,訪問共享資源前對互斥量加鎖,訪問完成後解鎖。當互斥量加鎖以後,其他所有需要訪問該互斥量的執行緒都將阻塞。當互斥量解鎖以後,所有因為這個互斥量阻塞的執行緒都將變為就緒態,第一個獲得cpu的執行緒會獲得互斥量,變為執行態,而其他執行緒會繼續變為阻塞,在這種方式下訪問互斥量每次只有一個執行緒能向前執行 - 加鎖和解鎖:
加鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
//成功返回0,失敗返回錯誤碼。如果互斥量已經被鎖住,那麼會導致該執行緒阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//成功返回0,失敗返回錯誤碼。如果互斥量已經被鎖住,不會導致執行緒阻塞
解鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0,失敗返回錯誤碼。如果一個互斥量沒有被鎖住,那麼解鎖就會出錯
例子:建立兩個執行緒,線上程中進行對全域性變數for迴圈自加,觀察有互斥鎖和沒有互斥鎖的全域性變數的結果
無互斥鎖:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
//定義一全域性變數
int val;
void *thread_fun(void *arg)
{
int i;
for(i=0;i<10000;i++)
{
val++;
}
return(void *)0;
}
int main()
{
pthread_t tid;
//創造新執行緒,這裡建立兩個新執行緒
pthread_create(&tid, NULL, thread_fun, NULL);
pthread_create(&tid, NULL, thread_fun, NULL);
//等待新執行緒執行結束
pthread_join(tid, NULL);
printf("val=%d\n",val);//列印經過兩次for迴圈後的val值,本應該是20000,但實際不是
return 0;
}
執行結果不為20000
有互斥鎖:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
//定義兩個全域性變數
int val;
pthread_mutex_t mutex;//這個為互斥量
void *thread_fun(void *arg)
{
int i;
//加鎖,防止其他執行緒進行加鎖,防止產生錯亂
pthread_mutex_lock(&mutex);
for(i=0;i<10000;i++)
{
val++;
}
//一次迴圈完成,列印結果一次
printf("一次迴圈完成,列印結果一次val=%d\n",val );
pthread_mutex_unlock(&mutex);//解鎖
return(void *)0;
}
int main()
{
pthread_t tid;
int err;
//對互斥量進行初始化,只有初始化過到互斥量才能使用
err = pthread_mutex_init(&mutex, NULL);
if(err != 0)
{
printf("init mutex failed\n");
return;
}
//創造新執行緒,這裡建立兩個新執行緒
pthread_create(&tid, NULL, thread_fun, NULL);
pthread_create(&tid, NULL, thread_fun, NULL);
//等待新執行緒執行結束
pthread_join(tid, NULL);
printf("最後得到的值val=%d\n",val);//列印經過兩次for迴圈後的val值
return 0;
}
執行結果:
2. 讀寫鎖
什麼是讀寫鎖,它與互斥鎖的區別:
- 讀寫鎖與互斥量類似,不過讀寫鎖有更高的並行性。互斥量要麼加鎖要麼不加鎖,而且同一時刻只允許一個執行緒對其加鎖。對於一個變數的讀取,完全可以讓多個執行緒同時進行操作
- pthread_rwlock_t rwlock
讀寫鎖有三種狀態,讀模式下加鎖,寫模式下加鎖,不加鎖。一次只有一個執行緒可以佔有寫模式下的讀寫鎖,但是多個執行緒可以同時佔有讀模式的讀寫鎖 - 讀寫鎖在寫加鎖狀態時,在它被解鎖之前,所有試圖對這個鎖加鎖的執行緒都會阻塞(無論是讀加鎖還是寫加鎖)。讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對其加鎖的執行緒都會獲得訪問權,但是如果執行緒希望以寫模式對其加鎖,它必須阻塞直到所有的執行緒釋放鎖。
- 當讀寫鎖讀模式加鎖時,如果有執行緒試圖以寫模式對其加鎖,那麼讀寫鎖會阻塞隨後的讀模式鎖請求。這樣可以避免讀鎖長期佔用,而寫鎖達不到請求。
- 讀寫鎖非常適合對資料結構讀次數大於寫次數的程式,當它以讀模式鎖住時,是以共享的方式鎖住的;當它以寫模式鎖住時,是以獨佔的模式鎖住的。
例子:建立兩個執行緒,分為以下三種情況:①執行緒一讀加鎖,執行緒二寫加鎖。②執行緒一讀加鎖,執行緒二讀加鎖。
情況①
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
pthread_rwlock_t rwlock;//定義一個讀寫鎖
int num=0;
void *thread_fun1(void *arg)
{
int err;
pthread_rwlock_rdlock(&rwlock);//讀模式下加鎖
printf("thread 1 is begin\n");
sleep(3);
printf("thread 1 over\n");
pthread_rwlock_unlock(&rwlock);
return (void *)1;
}
void *thread_fun2(void *arg)
{
int err;
pthread_rwlock_wrlock(&rwlock);//寫模式下加鎖
printf("thread 2 is begin\n");
sleep(3);
printf("thread 2 is over\n");
pthread_rwlock_unlock(&rwlock);
return (void *)1;
}
int main()
{
pthread_t tid1, tid2;
int err;
err = pthread_rwlock_init(&rwlock, NULL);//初始化讀寫鎖
if(err)
{
printf("init rwlock failed\n");
return ;
}
err = pthread_create(&tid1, NULL, thread_fun1, NULL);//建立新執行緒1
if(err)
{
printf("create new thread 1 failed\n");
return ;
}
err = pthread_create(&tid2, NULL, thread_fun2, NULL);//建立新執行緒2
if(err)
{
printf("create new thread 2 failed\n");
return ;
}
pthread_join(tid1, NULL);//連線執行緒,等待執行緒執行結束
pthread_join(tid2, NULL);
pthread_rwlock_destroy(&rwlock);//銷燬讀寫鎖
return 0;
}
執行結果:
第一次執行:讀寫鎖在讀加鎖狀態時,另外執行緒寫模式對其加鎖,它必須阻塞到讀加鎖執行緒釋放鎖。第二次執行:讀寫鎖在寫加鎖狀態時,在它被解鎖之前,所有試圖對這個鎖加鎖(無論是讀加鎖還是寫加鎖)的執行緒都會阻塞。
情況②:將執行緒二的啟動函式程式碼改為:
void *thread_fun2(void *arg)
{
int err;
pthread_rwlock_rdlock(&rwlock);//讀模式下加鎖
printf("thread 2 is begin\n");
sleep(3);
printf("thread 2 is over\n");
pthread_rwlock_unlock(&rwlock);
return (void *)1;
}
執行結果:
讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對其加鎖的執行緒都會獲得訪問權