日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux多线程】三个经典同步问题

發(fā)布時間:2025/3/21 linux 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Linux多线程】三个经典同步问题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在了解了《同步與互斥的區(qū)別》之后,我們來看看幾個經(jīng)典的線程同步的例子。相信通過具體場景可以讓我們學(xué)會分析和解決這類線程同步的問題,以便以后應(yīng)用在實際的項目中。

一、生產(chǎn)者-消費者問題

問題描述:

一組生產(chǎn)者進程和一組消費者進程共享一個初始為空、大小為 n 的緩沖區(qū),只有緩沖區(qū)沒滿時,生產(chǎn)者才能把消息放入到緩沖區(qū),否則必須等待;只有緩沖區(qū)不空時,消費者才能從中取出消息,否則必須等待。由于緩沖區(qū)是臨界資源,它只允許一個生產(chǎn)者放入消息,或者一個消費者從中取出消息。

分析:

  • 關(guān)系分析:生產(chǎn)者和消費者對緩沖區(qū)互斥訪問是互斥關(guān)系,同時生產(chǎn)者和消費者又是一個相互協(xié)作的關(guān)系,只有生產(chǎn)者生產(chǎn)之后,消費者才能消費,它們也是同步關(guān)系。

  • 整理思路:這里比較簡單,只有生產(chǎn)者和消費者兩個進程,且這兩個進程存在著互斥關(guān)系和同步關(guān)系。那么需要解決的是互斥和同步的PV操作的位置。

  • 信號量設(shè)置:信號量mutex作為互斥信號量,用于控制互斥訪問緩沖池,初值為1;信號量full用于記錄當(dāng)前緩沖池中“滿”緩沖區(qū)數(shù),初值為 0;信號量empty用于記錄當(dāng)前緩沖池中“空”緩沖區(qū)數(shù),初值為n。

  • 代碼示例:(semaphore類的封裝見下文)

    #include<iostream> #include<unistd.h> // sleep #include<pthread.h> #include"semaphore.h" using namespace std; #define N 5semaphore mutex("/", 1); // 臨界區(qū)互斥信號量 semaphore empty("/home", N); // 記錄空緩沖區(qū)數(shù),初值為N semaphore full("/home/songlee",0); // 記錄滿緩沖區(qū)數(shù),初值為0 int buffer[N]; // 緩沖區(qū),大小為N int i=0; int j=0;void* producer(void* arg) {empty.P(); // empty減1mutex.P();buffer[i] = 10 + rand() % 90;printf("Producer %d write Buffer[%d]: %d\n",arg,i+1,buffer[i]);i = (i+1) % N;mutex.V();full.V(); // full加1 }void* consumer(void* arg) {full.P(); // full減1mutex.P();printf(" \033[1;31m");printf("Consumer %d read Buffer[%d]: %d\n",arg,j+1,buffer[j]);printf("\033[0m");j = (j+1) % N;mutex.V();empty.V(); // empty加1 }int main() {pthread_t id[10];// 開10個生產(chǎn)者線程,10個消費者線程for(int k=0; k<10; ++k)pthread_create(&id[k], NULL, producer, (void*)(k+1));for(int k=0; k<10; ++k)pthread_create(&id[k], NULL, consumer, (void*)(k+1));sleep(1);return 0; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    編譯運行輸出結(jié)果:

    Producer 1 write Buffer[1]: 83 Producer 2 write Buffer[2]: 26 Producer 3 write Buffer[3]: 37 Producer 5 write Buffer[4]: 35 Producer 4 write Buffer[5]: 33Consumer 1 read Buffer[1]: 83 Producer 6 write Buffer[1]: 35Consumer 2 read Buffer[2]: 26Consumer 3 read Buffer[3]: 37Consumer 4 read Buffer[4]: 35Consumer 5 read Buffer[5]: 33Consumer 6 read Buffer[1]: 35 Producer 7 write Buffer[2]: 56 Producer 8 write Buffer[3]: 22 Producer 10 write Buffer[4]: 79Consumer 9 read Buffer[2]: 56Consumer 10 read Buffer[3]: 22 Producer 9 write Buffer[5]: 11Consumer 7 read Buffer[4]: 79Consumer 8 read Buffer[5]: 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20


    二、讀者-寫者問題

    問題描述:

    有讀者和寫者兩組并發(fā)線程,共享一個文件,當(dāng)兩個或以上的讀線程同時訪問共享數(shù)據(jù)時不會產(chǎn)生副作用,但若某個寫線程和其他線程(讀線程或?qū)懢€程)同時訪問共享數(shù)據(jù)時則可能導(dǎo)致數(shù)據(jù)不一致的錯誤。因此要求:

    • 允許多個讀者可以同時對文件執(zhí)行讀操作;
    • 只允許一個寫者往文件中寫信息;
    • 任一寫者在完成寫操作之前不允許其他讀者或?qū)懻吖ぷ?#xff1b;
    • 寫者執(zhí)行寫操作前,應(yīng)讓已有的讀者和寫者全部退出。

    分析:

  • 關(guān)系分析:由題目分析可知,讀者和寫者是互斥的,寫者和寫者也是互斥的,而讀者和讀者不存在互斥問題。

  • 整理思路:寫者是比較簡單的,它與任何線程互斥,用互斥信號量的 PV 操作即可解決。讀者的問題比較復(fù)雜,它必須實現(xiàn)與寫者的互斥,多個讀者還可以同時讀。所以,在這里用到了一個計數(shù)器,用它來判斷當(dāng)前是否有讀者讀文件。當(dāng)有讀者的時候?qū)懻呤菬o法寫文件的,此時讀者會一直占用文件,當(dāng)沒有讀者的時候?qū)懻卟趴梢詫懳募M瑫r,不同的讀者對計數(shù)器的訪問也應(yīng)該是互斥的。

  • 信號量設(shè)置:首先設(shè)置一個計數(shù)器count,用來記錄當(dāng)前的讀者數(shù)量,初值為0;設(shè)置互斥信號量mutex,用于保護更新 count 變量時的互斥;設(shè)置互斥信號量rw用于保證讀者和寫者的互斥訪問。

  • 代碼示例:

    #include<iostream> #include<unistd.h> // sleep #include<pthread.h> #include"semaphore.h" using namespace std;int count = 0; // 記錄當(dāng)前的讀者數(shù)量 semaphore mutex("/",1); // 用于保護更新count變量時的互斥 semaphore rw("/home",1); // 用于保證讀者和寫者的互斥void* writer(void* arg) {rw.P(); // 互斥訪問共享文件printf(" Writer %d start writing...\n", arg);sleep(1);printf(" Writer %d finish writing...\n", arg);rw.V(); // 釋放共享文件 }void* reader(void* arg) {mutex.P(); // 互斥訪問count變量if(count == 0) // 當(dāng)?shù)谝粋€讀線程讀文件時rw.P(); // 阻止寫線程寫++count; // 讀者計數(shù)器加1mutex.V(); // 釋放count變量printf("Reader %d start reading...\n", arg);sleep(1);printf("Reader %d finish reading...\n", arg);mutex.P(); // 互斥訪問count變量--count; // 讀者計數(shù)器減1if(count == 0) // 當(dāng)最后一個讀線程讀完文件rw.V(); // 允許寫線程寫mutex.V(); // 釋放count變量 }int main() {pthread_t id[8]; // 開6個讀線程,2個寫線程pthread_create(&id[0], NULL, reader, (void*)1);pthread_create(&id[1], NULL, reader, (void*)2);pthread_create(&id[2], NULL, writer, (void*)1);pthread_create(&id[3], NULL, writer, (void*)2);pthread_create(&id[4], NULL, reader, (void*)3);pthread_create(&id[5], NULL ,reader, (void*)4);sleep(2);pthread_create(&id[6], NULL, reader, (void*)5);pthread_create(&id[7], NULL ,reader, (void*)6);sleep(4);return 0; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    編譯運行的結(jié)果如下:

    Reader 2 start reading... Reader 1 start reading... Reader 3 start reading... Reader 4 start reading... Reader 1 finish reading... Reader 2 finish reading... Reader 3 finish reading... Reader 4 finish reading...Writer 1 start writing...Writer 1 finish writing...Writer 2 start writing...Writer 2 finish writing... Reader 5 start reading... Reader 6 start reading... Reader 5 finish reading... Reader 6 finish reading...
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16


    三、哲學(xué)家進餐問題

    問題描述:

    一張圓桌上坐著 5 名哲學(xué)家,桌子上每兩個哲學(xué)家之間擺了一根筷子,桌子的中間是一碗米飯,如圖所示:



    哲學(xué)家們傾注畢生精力用于思考和進餐,哲學(xué)家在思考時,并不影響他人。只有當(dāng)哲學(xué)家饑餓的時候,才試圖拿起左、右兩根筷子(一根一根拿起)。如果筷子已在他人手上,則需等待。饑餓的哲學(xué)家只有同時拿到了兩根筷子才可以開始進餐,當(dāng)進餐完畢后,放下筷子繼續(xù)思考。

    分析:

  • 關(guān)系分析:5名哲學(xué)家與左右鄰居對其中間筷子的訪問是互斥關(guān)系。

  • 整理思路:顯然這里有 5 個線程,那么要如何讓一個哲學(xué)家拿到左右兩個筷子而不造成死鎖或饑餓現(xiàn)象?解決方法有兩個,一個是讓他們同時拿兩個筷子;二是對每個哲學(xué)家的動作制定規(guī)則,避免饑餓或死鎖現(xiàn)象的發(fā)生。

  • 信號量設(shè)置:定義互斥信號量數(shù)組chopstick[5] = {1,1,1,1,1}用于對 5 根筷子的互斥訪問。

  • 示例代碼:

    semaphore chopstick[5] = {1,1,1,1,1} // 信號量數(shù)組 Pi() // i號哲學(xué)家的線程 {do{P(chopstick[i]); // 取左邊筷子P(chopstick[(i+1)%5]); // 取右邊筷子eat; // 進餐V(chopstick[i]); // 放回左邊筷子V(chopstick[(i+1)%5]); // 放回右邊筷子think; // 思考}while(1); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的偽代碼存在一個問題:當(dāng)五個哲學(xué)家都想要進餐,分別拿起他們左邊筷子的時候(都恰好執(zhí)行完P(guān)(chopstick[i])),筷子已經(jīng)被拿光了,等到他們再想拿右邊的筷子的時候,就全被阻塞了,這就出現(xiàn)了死鎖。

    為了防止死鎖的發(fā)生,可以對哲學(xué)家線程施加一些限制條件,比如:

    • 至多允許四個哲學(xué)家同時進餐;
    • 僅當(dāng)一個哲學(xué)家左右兩邊的筷子都可用時才允許他抓起筷子;
    • 對哲學(xué)家順序編號,要求奇數(shù)號哲學(xué)家先抓左邊的筷子,然后再抓他右邊的筷子,而偶數(shù)號哲學(xué)家剛好相反。

    這里,我們采用第二種方法來改進上面的算法,即當(dāng)一個哲學(xué)家左右兩邊的筷子都可用時,才允許他抓起筷子。

    #include<iostream> #include<vector> #include<unistd.h> // sleep #include<pthread.h> #include"semaphore.h" using namespace std;vector<semaphore*> chopstick; // 信號量數(shù)組 semaphore mutex("/", 1); // 設(shè)置取左右筷子的信號量 <-- 關(guān)鍵void* P1(void* arg) // 第1個哲學(xué)家線程 {mutex.P(); // 在取筷子前獲得互斥量chopstick[0]->P(); // 取左邊筷子chopstick[1]->P(); // 取右邊筷子mutex.V(); // 釋放取筷子的信號量printf("Philosopher 1 eat.\n");chopstick[0]->V(); // 放回左邊筷子chopstick[1]->V(); // 放回右邊筷子 }void* P2(void* arg) // 第2個哲學(xué)家線程 {mutex.P(); // 在取筷子前獲得互斥量chopstick[1]->P(); // 取左邊筷子chopstick[2]->P(); // 取右邊筷子mutex.V(); // 釋放取筷子的信號量printf("Philosopher 2 eat.\n");chopstick[1]->V(); // 放回左邊筷子chopstick[2]->V(); // 放回右邊筷子 }void* P3(void* arg) // 第3個哲學(xué)家線程 {mutex.P(); // 在取筷子前獲得互斥量chopstick[2]->P(); // 取左邊筷子chopstick[3]->P(); // 取右邊筷子mutex.V(); // 釋放取筷子的信號量printf("Philosopher 3 eat.\n");chopstick[2]->V(); // 放回左邊筷子chopstick[3]->V(); // 放回右邊筷子 }void* P4(void* arg) // 第4個哲學(xué)家線程 {mutex.P(); // 在取筷子前獲得互斥量chopstick[3]->P(); // 取左邊筷子chopstick[4]->P(); // 取右邊筷子mutex.V(); // 釋放取筷子的信號量printf("Philosopher 4 eat.\n");chopstick[3]->V(); // 放回左邊筷子chopstick[4]->V(); // 放回右邊筷子 }void* P5(void* arg) // 第5個哲學(xué)家線程 {mutex.P(); // 在取筷子前獲得互斥量chopstick[4]->P(); // 取左邊筷子chopstick[0]->P(); // 取右邊筷子mutex.V(); // 釋放取筷子的信號量printf("Philosopher 5 eat.\n");chopstick[4]->V(); // 放回左邊筷子chopstick[0]->V(); // 放回右邊筷子 }int main() {semaphore *sem1 = new semaphore("/home", 1);semaphore *sem2 = new semaphore("/home/songlee", 1);semaphore *sem3 = new semaphore("/home/songlee/java", 1);semaphore *sem4 = new semaphore("/home/songlee/ADT", 1);semaphore *sem5 = new semaphore("/home/songlee/Test", 1);chopstick.push_back(sem1);chopstick.push_back(sem2);chopstick.push_back(sem3);chopstick.push_back(sem4);chopstick.push_back(sem5);pthread_t id;pthread_create(&id, NULL, P1, NULL);pthread_create(&id, NULL, P2, NULL);pthread_create(&id, NULL, P3, NULL);pthread_create(&id, NULL, P4, NULL);pthread_create(&id, NULL, P5, NULL);sleep(1);delete sem1;delete sem2;delete sem3;delete sem4;delete sem5;return 0; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    編譯運行的結(jié)果如下:

    Philosopher 2 eat. Philosopher 1 eat. Philosopher 3 eat. Philosopher 4 eat. Philosopher 5 eat.
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:創(chuàng)建信號量時的 路徑參數(shù) 請改成你的系統(tǒng)中存在的路徑!!!





    附:semaphore類的封裝

    上面的代碼中都使用了這個semaphore類,實現(xiàn)如下:

    • semaphore.h
    #pragma once #include<iostream> #include<cstdio> #include<cstdlib> #include<sys/sem.h> using namespace std;// 聯(lián)合體,用于semctl初始化 union semun {int val; /*for SETVAL*/struct semid_ds *buf;unsigned short *array; };class semaphore { private:int sem_id;int init_sem(int); public:semaphore(const char*, int); /*構(gòu)造函數(shù)*/~semaphore(); /*析構(gòu)函數(shù)*/void P(); /*P操作*/void V(); /*V操作*/ };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • semaphore.cpp
    #include"semaphore.h"semaphore::semaphore(const char* path, int value) {key_t key;/*獲取key值*/if((key = ftok(path, 'z')) < 0) {perror("ftok error");exit(1);}/*創(chuàng)建信號量集,其中只有一個信號量*/if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1) {perror("semget error");exit(1);}init_sem(value); }semaphore::~semaphore() {union semun tmp;if(semctl(sem_id, 0, IPC_RMID, tmp) == -1) {perror("Delete Semaphore Error");exit(1);} }void semaphore::P() {struct sembuf sbuf;sbuf.sem_num = 0; /*序號*/sbuf.sem_op = -1; /*P操作*/sbuf.sem_flg = SEM_UNDO;if(semop(sem_id, &sbuf, 1) == -1) {perror("P operation Error");} }void semaphore::V() {struct sembuf sbuf;sbuf.sem_num = 0; /*序號*/sbuf.sem_op = 1; /*V操作*/sbuf.sem_flg = SEM_UNDO;if(semop(sem_id, &sbuf, 1) == -1) {perror("V operation Error");} }// 初始化信號量 int semaphore::init_sem(int value) {union semun tmp;tmp.val = value;if(semctl(sem_id, 0, SETVAL, tmp) == -1) {perror("Init Semaphore Error");return -1;}return 0; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    在這里,要創(chuàng)建不同的信號量,必須傳遞不同的路徑參數(shù)(這樣獲取的 key 值才會不一樣)。

    注意,本文的關(guān)注點并不在于 linux 下如何創(chuàng)建信號量以及如何封裝起來才更方便,而是通過幾個經(jīng)典的同步實例,了解在多線程環(huán)境下如何解決這類線程同步問題。







    個人站點:http://songlee24.github.com

    總結(jié)

    以上是生活随笔為你收集整理的【Linux多线程】三个经典同步问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。