天天看點

Linux系統程式設計——線程同步

在學習Linux系統程式設計總結了筆記,并分享出來。有問題請及時聯系部落客:​​Alliswell_WP​​,轉載請注明出處。

09-linux-day09(線程同步)

目錄:一、内容回顧二、學習目标三、線程同步1、互斥量的使用2、死鎖3、讀寫鎖4、條件變量介紹-生産者和消費者模型5、條件變量生産者消費者模型實作6、條件變量生産者和消費者模型示範7、信号量的概念和函數8、信号量實作生産者和消費者分析9、信号量實作生産者和消費者10、檔案鎖單開程序11、哲學家就餐模型分析

一、内容回顧

1、守護程序:運作在背景,脫離終端,周期性執行某些任務

會話:多個程序組組成一個會話,組長程序不能建立會話。

程序組:多個程序在同一個組,第一個程序預設是組長

守護程序的步驟:

(1)建立子程序,父親退出(孤兒程序)

(2)建立會話,當會長

(3)切換目錄

(4)設定掩碼

(5)關閉檔案描述符

(6)執行核心邏輯

(7)退出

nohup & 把程序放入背景運作

2、多線程:

線程是輕量級的程序,最小的執行機關,程序是最小的資源申請機關。一個程序裡可以有多個線程。

建立線程 pthread_create

回收線程 pthred_join

線程退出 pthread_exit void *retval

殺死線程 pthread_cancel 取消點

線程分離 pthread_detach,也可以通過屬性設定

pthread_attr_setdetachstate 設定屬性分離,之前需要pthread_attr_init初始化,之後需要pthread_attr_destroy銷毀

檢視線程ID:pthread_self 在程序内唯一

二、學習目标

1、熟練掌握互斥量的使用

2、說出什麼叫死鎖以及解決方案

3、熟練掌握讀寫死鎖的使用

4、熟練掌握條件變量的使用

5、了解條件變量實作的生産者消費者模型

6、了解信号量實作的生産者消費者模型

三、線程同步

1、互斥量的使用

》互斥量

兩個線程通路同一塊共享資源,如果不協調順序,容易造成資料混亂。

>加鎖 mutex

pthread_mutex_init 初始化

或:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_destroy 摧毀

pthread_mutex_lock 加鎖

pthread_mutex_unlock(pthread_mutex_t *mutex) 解鎖

》互斥量的使用步驟:

1)初始化

2)加鎖

3)執行邏輯——操作共享資料

4)解鎖

注意事項:

  加鎖需要最小力度,不要一直占用臨界區。

解決昨天的問題——模拟線程共同搶占(螢幕)資源

>vi pthread_print.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 
 6 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 7 
 8  9 
10 void* thr1(void *arg){
11     while(1){
12         //先上鎖
13         pthread_mutex_lock(&mutex);//加鎖:當有線程已經加鎖的時候會阻塞
14         //臨界區
15         printf("hello");//不帶換行,沒有行緩沖,輸出不出來
16         sleep(rand()%3);
17         printf("world\n");
18         //釋放鎖
19         pthread_mutex_unlock(&mutex);
20         sleep(rand()%3);
21     }
22 }
23 
24 void* thr2(void *arg){
25     while(1){
26         //先上鎖
27         pthread_mutex_lock(&mutex);
28         //臨界區
29         printf("HELLO");
30         sleep(rand()%3);
31         printf("WORLD\n");
32         //釋放鎖
33         pthread_mutex_unlock(&mutex);
34         sleep(rand()%3);
35     }
36 }
37 
38 int main(int argc, char *argv[])
39 {
40     //建立兩個線程,回收兩次
41     pthread_t tid[2];
42     pthread_create(&tid[0],NULL,thr1,NULL);
43     pthread_create(&tid[1],NULL,thr2,NULL);
44     
45     pthread_join(tid[0],NULL);
46     pthread_join(tid[1],NULL);
47     
48     return 0;
49 }      

>make

>./pthread_print

解決昨天的問題——模拟線程共同搶占(緩沖區)資源

>vi thr_write

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 #include<sys/types.h>
 6 #include<sys/stat.h>
 7 #include<fcntl.h>
 8 #include<string.h>
 9 
10 pthread_mutex_t mutex;
11 char buf[20];
12 
13 void* thr1(void *arg){
14     int i = 0;
15     pthread_mutex_lock(&mutex);
16     for(;i < 20; i++){
17         usleep(rand()%3);
18         buf[i] = '0';
19     }
20     pthread_mutex_unlock(&mutex);
21     return NULL;
22 }
23 
24 void* thr2(void *arg){
25     int i = 0;
26     pthread_mutex_lock(&mutex);
27     for(;i < 20; i++){
28         usleep(rand()%3);
29         buf[i] = '1';
30     }
31     pthread_mutex_unlock(&mutex);
32     return NULL;
33 }
34 
35 int main(int argc, char *argv[])
36 {
37     //建立兩個線程,回收兩次
38     memset(buf,0x00,sizeof(buf));
39     
40     pthread_mutex_init(&mutex,NULL);
41     
42     pthread_t tid[2];
43     pthread_create(&tid[0],NULL,thr1,NULL);
44     pthread_create(&tid[1],NULL,thr2,NULL);
45     
46     pthread_join(tid[0],NULL);
47     pthread_join(tid[1],NULL);
48     printf("buf is %s\n",buf);
49     
50     pthread_mutex_destroy(&mutex);
51     
52     return 0;
53 }      

>make

>./thr_write

》嘗試加鎖

man pthread_mutex_trylock

int pthread_mutex_trylock(pthread_mutex_t *mutex);

測試(已經加鎖的再次嘗試加鎖結果會怎麼樣?)

>touch mutex_trylock.c

>vi mutex_trylock.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<string.h>
 5 
 6 pthread_mutex_t mutex;
 7 
 8 void *thr(void *arg)
 9 {
10     while(1){
11         pthread_mutex_lock(&mutex);
12         printf("hello world\n");
13         sleep(30);
14         pthread_mutex_unlock(&mutex);
15     }
16     return NULL;
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     pthread_mutex_init(&mutex,NULL);
22     pthread_t tid;
23     pthread_create(&tid,NULL,thr,NULL);
24     sleep(1);
25     while(1){
26         int ret = pthread_mutex_trylock(&mutex);
27         if(ret > 0){
28             printf("ret = %d,errmmsg:%s\n",ret,strerror(ret));
29         }
30         sleep(1);
31     }
32     
33     return 0;
34 }      

>make

>./mutex_trylock

(列印完hellow world後,一直列印:ret = 16,errmsg:Device or resource busy)

可以在以下檔案檢視errno的錯誤資訊

Linux系統程式設計——線程同步
Linux系統程式設計——線程同步

2、死鎖

》死鎖

  1)鎖了又鎖,自己加了一次鎖成功,又加了一次鎖。

   2)交叉鎖(解決辦法:每個線程申請鎖的順序要一緻。如果申請到一把鎖,申請另外一把的時候申請失敗,應該釋放已經掌握的。)

注意:互斥量隻是建議鎖。

3、讀寫鎖

》讀寫鎖

  與互斥量類似,但讀寫鎖允許更高的并行性。其特點為:讀共享,寫獨占,寫優先級高。

》讀寫鎖狀态:

三種狀态:

  1)讀模式下加鎖狀态(讀鎖)

  2)寫模式下加鎖狀态(寫鎖)

  3)不加鎖狀态

》讀寫鎖特性:

1)讀寫鎖是“寫模式加鎖”時,解鎖前,所有對該鎖加鎖的線程都會被阻塞。

2)讀寫鎖是“讀模式加鎖”時,如果線程以讀模式對其加鎖會成功;如果線程以寫模式加鎖會阻塞。

3)讀寫鎖是“讀模式加鎖”時,既有試圖以寫模式加鎖的線程,也有試圖以讀模式加鎖的線程。那麼讀寫鎖會阻塞随後的讀模式鎖請求。優先滿足寫模式鎖。讀鎖、寫鎖并行阻塞,寫鎖優先級高。

讀寫鎖也叫共享-獨占鎖。當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當它以寫模式鎖住時,它是以獨占模式鎖住的。讀共享,寫獨占。

》讀寫鎖的使用場景:非常适合于對資料結構讀的次數遠大于寫的情況。

》讀寫鎖場景練習:

1)線程A加寫鎖成功,線程B請求讀鎖

  B阻塞

2)線程A持有讀鎖,線程B請求寫鎖

  B阻塞

3)線程A擁有讀鎖,線程B請求讀鎖

  B加鎖成功

4)線程A持有讀鎖,然後線程B請求寫鎖,然後線程C請求寫鎖

  BC阻塞;A釋放後,B加鎖;B釋放後,C加鎖

5)線程A持有寫鎖,然後線程B請求讀鎖,然後線程C請求寫鎖

  BC阻塞;A釋放後,C加鎖;C釋放後,B加鎖

》初始化:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

》銷毀讀寫鎖

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

》加讀鎖

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

》加寫鎖

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

》釋放鎖

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

練習:

>touch rwlock.c

>vi rwlock.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 
 5 //初始化
 6 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
 7 int beginnum = 1000;
 8 
 9 void *thr_write(void *arg)
10 {
11     while(1){
12         pthread_rwlock_wrlock(&rwlock);
13         printf("---%s---self---%lu---beginnum---%d\n",__FUNCTION__,pthread_self(),++beginnum);
14         usleep(2000);//模拟占用時間2ms
15         pthread_rwlock_unlock(&rwlock);
16         usleep(4000);//防止再次搶去
17     }
18     return NULL;
19 }
20 
21 void *thr_read(void *arg)
22 {
23     while(1){
24         pthread_rwlock_rdlock(&rwlock);
25         printf("---%s---self---%lu---beginnum---%d\n",__FUNCTION__,pthread_self(),beginnum);
26         usleep(2000);//模拟占用時間2ms
27         pthread_rwlock_unlock(&rwlock);
28         usleep(2000);//防止再次搶去
29     }
30     return NULL;
31 }
32 
33 int main(int argc, char *argv[])
34 {
35     int n = 8,i = 0;
36     pthread_t tid[8];
37     //5-read, 3-write
38     for(i = 0; i < 5; i++){
39         pthread_create(&tid[i],NULL,thr_read,NULL);
40     }
41     for(;i < 8; i++){
42         pthread_create(&tid[i],NULL,thr_write,NULL);
43     }
44     
45     for(i = 0; i < 8; i++){
46         pthread_join(tid[i],NULL);
47     }
48     
49     return 0;
50 }      

>make

>./rwlock

4、條件變量介紹-生産者和消費者模型

條件變量不是鎖,要和互斥量組合使用!

》條件變量:可以引起阻塞,并非鎖

競争者可以阻塞在是否有餅這個條件上?

》條件變量的優點:

相對于mutex而言,條件變量可以減少競争。

如直接使用mutex,除了生産者、消費者之間要競争互斥量以外,消費者之間也需要競争互斥量,但如果彙聚(連結清單)中沒有資料,消費者之間競争互斥鎖是無意義的。有了條件機制以後,隻有生産者完成生産,才會引起消費者之間的競争。提高了程式效率。

》初始化一個條件變量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

》銷毀

int pthread_cond_destroy(pthread_cond_t *cond);

》阻塞等待

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

  先釋放鎖mutex;然後阻塞在cond條件變量上。

》限時(逾時)等待

int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

struct timespec{

  time_t tc_sec; /*seconds*/ 秒

  long tv_nsec;/*nanoseconds*/納秒

};

tv_sec 絕對時間,填寫的時候 time(NULL) + 600 ---> 設定逾時600s

》喚醒至少一個阻塞在條件變量cond上的線程

int pthread_cond_signal(pthread_cond_t *cond);

》喚醒阻塞在條件變量cond上的全部線程

int pthread_cond_broadcast(pthread_cond_t *cond);

5、條件變量生産者消費者模型實作

》條件變量的作用:避免無必要的競争

練習:(先模拟一個生産者,一個消費者)

>touch cond_product.c

>vi cond_product.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 
 6 //初始化
 7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 8 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 9 
10 int beginnum = 1000;
11 
12 typedef struct _ProInfo{
13     int num;
14     struct _ProInfo *next;
15 }ProInfo;
16 
17 ProInfo *Head = NULL;
18 
19 void *thr_producter(void *arg)
20 {
21     //負責在連結清單添加資料
22     while(1){
23         ProInfo *prod = malloc(sizeof(ProInfo));
24         prod->num = beginnum++;
25         printf("---%s---self=%lu---%d\n",__FUNCTION__,pthread_self(),prod->num);
26         pthread_mutex_lock(&mutex);
27         //add to list
28         prod->next = Head;
29         Head = prod;
30         pthread_mutex_unlock(&mutex);
31         //發起通知
32         pthread_cond_signal(&cond);
33         sleep(rand()%4);
34     }
35     return NULL;
36 }
37 
38 void *thr_customer(void *arg)
39 {
40     ProInfo *prod = NULL;
41     while(1){
42         //取連結清單的資料
43         pthread_mutex_lock(&mutex);
44         if(Head == NULL){
45             pthread_cond_wait(&cond,&mutex);//在此之前必須先加鎖
46         }
47         prod = Head;
48         Head = Head->next;
49         printf("---%s---self=%lu---%d\n",__FUNCTION__,pthread_self(),prod->num);
50         pthread_mutex_unlock(&mutex);
51         sleep(rand()%4);
52         free(prod);
53     }
54     return NULL;
55 }
56 
57 
58 int main(int argc, char *argv[])
59 {
60     pthread_t tid[2];
61     pthread_create(&tid[0],NULL,thr_producter,NULL);
62     pthread_create(&tid[1],NULL,thr_customer,NULL);
63     
64     pthread_join(tid[0],NULL);
65     pthread_join(tid[1],NULL);
66     
67     pthread_mutex_destroy(&mutex);
68     pthread_cond_destroy(&cond);
69     
70     return 0;
71 }      

>make

>./cond_product

(問題:如果消費者增多,消費者判斷可以消費,都進入,可能消費者都卡在阻塞等待那)

6、條件變量生産者和消費者模型示範

解決:

>vi cond_product.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<stdlib.h>
 5 
 6 //初始化
 7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 8 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 9 
10 int beginnum = 1000;
11 
12 typedef struct _ProInfo{
13     int num;
14     struct _ProInfo *next;
15 }ProInfo;
16 
17 ProInfo *Head = NULL;
18 
19 void *thr_producter(void *arg)
20 {
21     //負責在連結清單添加資料
22     while(1){
23         ProInfo *prod = malloc(sizeof(ProInfo));
24         prod->num = beginnum++;
25         printf("---%s---self=%lu---%d\n",__FUNCTION__,pthread_self(),prod->num);
26         pthread_mutex_lock(&mutex);
27         //add to list
28         prod->next = Head;
29         Head = prod;
30         pthread_mutex_unlock(&mutex);
31         //發起通知
32         pthread_cond_signal(&cond);
33         sleep(rand()%2);
34     }
35     return NULL;
36 }
37 
38 void *thr_customer(void *arg)
39 {
40     ProInfo *prod = NULL;
41     while(1){
42         //取連結清單的資料
43         pthread_mutex_lock(&mutex);
44         while(Head == NULL){//if更改為while
45             pthread_cond_wait(&cond,&mutex);//在此之前必須先加鎖
46         }
47         prod = Head;
48         Head = Head->next;
49         printf("---%s---self=%lu---%d\n",__FUNCTION__,pthread_self(),prod->num);
50         pthread_mutex_unlock(&mutex);
51         sleep(rand()%4);
52         free(prod);
53     }
54     return NULL;
55 }
56 
57 
58 int main(int argc, char *argv[])
59 {
60     pthread_t tid[3];
61     pthread_create(&tid[0],NULL,thr_producter,NULL);
62     pthread_create(&tid[1],NULL,thr_customer,NULL);
63     pthread_create(&tid[2],NULL,thr_customer,NULL);
64     
65     pthread_join(tid[0],NULL);
66     pthread_join(tid[1],NULL);
67     pthread_join(tid[2],NULL);
68     
69     pthread_mutex_destroy(&mutex);
70     pthread_cond_destroy(&cond);
71     
72     return 0;
73 }      

>make

>./cond_product

7、信号量的概念和函數

》信号量:加強版(進化版)的互斥鎖,允許多個線程通路共享資源

Linux系統程式設計——線程同步
Linux系統程式設計——線程同步

以上6個函數的傳回值都是:成功傳回0,失敗傳回-1,同時設定errno。

注意:它們沒有pthread字首!

sem_t類型,本質仍是結構體。但應用期間可簡單看作為整數,忽略實作細節(類似于使用檔案描述符)。

sem_t sem; 規定信号量sem不能 < 0。頭檔案<semaphore.h>

》初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);

  sem 定義的信号量,傳出

  pshared:0代表線程信号量;非0代表程序信号量

  value:定義信号量的個數

》摧毀信号量

int sem_destroy(sem_t *sem);

》申請信号量,申請成功value--

int sem_wait(sem_t *sem);

  當信号量為0時,阻塞

》釋放信号量 value++

int sem_post(sem_t *sem);

Linux系統程式設計——線程同步

8、信号量實作生産者和消費者分析

Linux系統程式設計——線程同步

9、信号量實作生産者和消費者

>touch sem_product.c

>vi sem_product.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<pthread.h>
 4 #include<semaphore.h>
 5 #include<stdlib.h>
 6 
 7 sem_t blank,xfull;
 8 #define _SEM_CNT_ 5
 9 
10 int queue[_SEM_CNT_];//模拟餅筐
11 int beginnum = 100;
12 
13 void *thr_producter(void *arg)
14 {
15     int i = 0;
16     while(1){
17         sem_wait(&blank);//申請資源 blank--
18         printf("---%s---self=%lu---num=%d\n",__FUNCTION__,pthread_self(),beginnum);
19         queue[(i++)%_SEM_CNT_] = beginnum++;
20         sem_post(&xfull);//xfull++
21         sleep(rand()%3);
22     }
23     return NULL;
24 }
25 
26 void *thr_customer(void *arg)
27 {
28     int i = 0;
29     int num = 0;
30     while(1){
31         sem_wait(&xfull);
32         num = queue[(i++)%_SEM_CNT_];
33         printf("---%s---self=%lu---num=%d\n",__FUNCTION__,pthread_self(),num);
34         sem_post(&blank);
35         sleep(rand()%3);
36     }
37     return NULL;
38 }
39 
40 int main(int argc, char *argv[])
41 {
42     sem_init(&blank,0,_SEM_CNT_);
43     sem_init(&xfull,0,0);//消費者一開始的初始化預設沒有産品
44     
45     pthread_t tid[2];
46     
47     pthread_create(&tid[0],NULL,thr_producter,NULL);
48     pthread_create(&tid[1],NULL,thr_customer,NULL);
49     
50     pthread_join(tid[0],NULL);
51     pthread_join(tid[1],NULL);
52     
53     sem_destroy(&blank);
54     sem_destroy(&xfull);
55     
56     return 0;
57 }      

>make

>./sem_product

10、檔案鎖單開程序

》互斥量mutex

\

Linux系統程式設計——線程同步

》檔案鎖

Linux系統程式設計——線程同步
Linux系統程式設計——線程同步

>touch flock.c

>vi flock.c

1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<sys/types.h>
 4 #include<sys/stat.h>
 5 #include<fcntl.h>
 6 #include<stdlib.h>
 7 
 8 #define _FILE_NAME_ "/home/wang/temp.lock"
 9 
10 int main(int argc, char *argv[])
11 {
12     int fd = open(_FILE_NAME_,O_RDWR | O_CREAT,0666);
13     if(fd < 0){
14         perror("open err");
15         return -1;
16     }
17     struct flock lk;
18     lk.l_type = F_WRLCK;
19     lk.l_whence = SEEK_SET;
20     lk.l_start = 0;
21     lk.l_len = 0;
22     
23     if(fcntl(fd,F_SETLK,&lk) < 0){
24         perror("get lock err");
25         exit(1);
26     }
27     
28     //核心邏輯
29     while(1){
30         printf("I am alive!\n");
31         sleep(1);
32     }
33     
34     return 0;
35 }      

>make

>./flock

(打開另一個終端,運作./flock,提示:get lock err: Resource temporarily unavailable,說明檔案已被加鎖,隻能啟動一個程序了。)

11、哲學家就餐模型分析

Linux系統程式設計——線程同步
Linux系統程式設計——線程同步

作業