linux線程同步

互斥鎖

同步與互斥概述**

現代操作系統基本都是多任務操作系統,即同時有大量可調度實體在運行。在多任務操作系統中,同時運行的多個任務可能:

  • 都需要訪問/使用同一種資源

  • 多個任務之間有依賴關系,某個任務的運行依賴于另一個任務

這兩種情形是多任務編程中遇到的最基本的問題,也是多任務編程中的核心問題,同步和互斥就是用于解決這兩個問題的。

互斥:是指散步在不同任務之間的若干程序片斷,當某個任務運行其中一個程序片段時,其它任務就不能運行它們之中的任一程序片段,只能等到該任務運行完這個程序片段后才可以運行。最基本的場景就是:一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。

同步:是指散步在不同任務之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先后次序來運行,這種先后次序依賴于要完成的特定的任務。最基本的場景就是:兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先后次序運行。比如 A 任務的運行依賴于 B 任務產生的數據。

顯然,同步是一種更為復雜的互斥,而互斥是一種特殊的同步。也就是說互斥是兩個任務之間不可以同時運行,他們會相互排斥,必須等待一個線程運行完畢,另一個才能運行,而同步也是不能同時運行,但他是必須要按照某種次序來運行相應的線程(也是一種互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任務的運行順序,即任務是無序的,而同步的任務之間則有順序關系。

為什么需要互斥鎖

在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。這個過程有點類似于,公司部門里,我在使用著打印機打印東西的同時(還沒有打印完),別人剛好也在此刻使用打印機打印東西,如果不做任何處理的話,打印出來的東西肯定是錯亂的。

下面我們用程序模擬一下這個過程,線程一需要打印“ hello ”,線程二需要打印“ world ”,不加任何處理的話,打印出來的內容會錯亂:

測試程序:

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  /*       *       模擬一下這個過程,線程一需要打印“ hello ”,線程二需要打印“ world ”,*       不加任何處理的話,打印出來的內容會錯亂:*/void printer(char *str)
{while (*str != '\0'){putchar(*str);fflush(stdout);str++;sleep(1);}printf("\n");
}//線程一
void *thread_fun_1(void *arg)
{char *str = "hello";printer(str); //打印
}//線程二
void *thread_fun_2(void *arg)
{char *str = "world";printer(str); //打印
}int main()
{pthread_t tid1, tid2;//創建 2 個線程pthread_create(&tid1, NULL, thread_fun_1, NULL);pthread_create(&tid2, NULL, thread_fun_2, NULL);//等待線程結束,回收其資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

運行結果如下:

實際上,打印機是有做處理的,我在打印著的時候別人是不允許打印的,只有等我打印結束后別人才允許打印。這個過程有點類似于,把打印機放在一個房間里,給這個房間安把鎖,這個鎖默認是打開的。當 A 需要打印時,他先過來檢查這把鎖有沒有鎖著,沒有的話就進去,同時上鎖在房間里打印。而在這時,剛好 B 也需要打印,B 同樣先檢查鎖,發現鎖是鎖住的,他就在門外等著。而當 A 打印結束后,他會開鎖出來,這時候 B 才進去上鎖打印。

互斥鎖Mutex介紹

而在線程里也有這么一把鎖:互斥鎖(mutex),也叫互斥量,互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即加鎖( lock )和解鎖( unlock )。

互斥鎖的操作流程如下:

1)在訪問共享資源后臨界區域前,對互斥鎖進行加鎖。

2)在訪問完成后釋放互斥鎖導上的鎖。

3)對互斥鎖進行加鎖后,任何其他試圖再次對互斥鎖加鎖的線程將會被阻塞,直到鎖被釋放。

互斥鎖的數據類型是: pthread_mutex_t。

安裝對應幫助手冊:

sudo apt-get install manpages-posix-dev

pthread_mutex_init 函數

初始化互斥鎖:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr);

功能:

????????初始化一個互斥鎖。

參數:

????????mutex:互斥鎖地址。類型是 pthread_mutex_t 。

????????attr:設置互斥量的屬性,通常可采用默認屬性,即可將 attr 設為 NULL。

可以使用宏 PTHREAD_MUTEX_INITIALIZER 靜態初始化互斥鎖,比如:

pthread_mutex_t ?mutex = PTHREAD_MUTEX_INITIALIZER;

這種方法等價于使用 NULL 指定的 attr 參數調用 pthread_mutex_init() 來完成動態初始化,不同之處在于 PTHREAD_MUTEX_INITIALIZER 宏不進行錯誤檢查。

返回值:

成功:0,成功申請的鎖默認是打開的。

失敗:非 0 錯誤碼

restrict,C語言中的一種類型限定符(Type Qualifiers),用于告訴編譯器,對象已經被指針所引用,不能通過除該指針外所有其他直接或間接的方式修改該對象的內容。

pthread_mutex_destroy函數

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:

銷毀指定的一個互斥鎖。互斥鎖在使用完畢后,必須要對互斥鎖進行銷毀,以釋放資源。

參數:

mutex:互斥鎖地址。

返回值:

成功:0

失敗:非 0 錯誤碼

pthread_mutex_lock函數

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:

對互斥鎖上鎖,若互斥鎖已經上鎖,則調用者阻塞,直到互斥鎖解鎖后再上鎖。

參數:

mutex:互斥鎖地址。

返回值:

成功:0

失敗:非 0 錯誤碼

int pthread_mutex_trylock(pthread_mutex_t *mutex);

  • 調用該函數時,若互斥鎖未加鎖,則上鎖,返回 0;
  • 若互斥鎖已加鎖,則函數直接返回失敗,即 EBUSY。

pthread_mutex_unlock函數

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:

????????對指定的互斥鎖解鎖。

參數:

????????mutex:互斥鎖地址。

返回值:

????????成功:0

????????失敗:非0錯誤碼

測試程序

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  /*       *       模擬一下這個過程,線程一需要打印“ hello ”,線程二需要打印“ world ”,*       加鎖的代碼,此時打印的結果不會錯亂*/pthread_mutex_t mutex; //互斥鎖//打印機
void printer(char *str)
{pthread_mutex_lock(&mutex); //上鎖while (*str != '\0'){putchar(*str);fflush(stdout);str++;sleep(1);}printf("\n");pthread_mutex_unlock(&mutex); //解鎖
}//線程一
void *thread_fun_1(void *arg)
{char *str = "hello";printer(str); //打印
}//線程二
void *thread_fun_2(void *arg)
{char *str = "world";printer(str); //打印
}int main(void)
{pthread_t tid1, tid2;pthread_mutex_init(&mutex, NULL); //初始化互斥鎖//創建 2 個線程pthread_create(&tid1, NULL, thread_fun_1, NULL);pthread_create(&tid2, NULL, thread_fun_2, NULL);//等待線程結束,回收其資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&mutex); //銷毀互斥鎖return 0;
}

結果:

死鎖(DeadLock)

1)什么是死鎖

死鎖是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。

2)死鎖引起的原因

競爭不可搶占資源引起死鎖

也就是我們說的第一種情況,而這都在等待對方占有的不可搶占的資源。

競爭可消耗資源引起死鎖

有p1,p2,p3三個進程,p1向p2發送消息并接受p3發送的消息,p2向p3發送消息并接受p1的消息,p3向p1發送消息并接受p2的消息,如果設置是先接到消息后發送消息,則所有的消息都不能發送,這就造成死鎖。

進程推進順序不當引起死鎖

有進程p1,p2,都需要資源A,B,本來可以p1運行A --> p1運行B --> p2運行A --> p2運行B,但是順序換了,p1運行A時p2運行B,容易發生第一種死鎖。互相搶占資源。

3)死鎖的必要條件

互斥條件

  • 某資源只能被一個進程使用,其他進程請求該資源時,只能等待,直到資源使用完畢后釋放資源。

請求和保持條件

  • 程序已經保持了至少一個資源,但是又提出了新要求,而這個資源被其他進程占用,自己占用資源卻保持不放。

不可搶占條件

  • 進程已獲得的資源沒有使用完,不能被搶占。

循環等待條件

  • 必然存在一個循環鏈。

4)處理死鎖的思路

預防死鎖

????????破壞死鎖的四個必要條件中的一個或多個來預防死鎖。

避免死鎖

????????和預防死鎖的區別就是,在資源動態分配過程中,用某種方式防止系統進入不安全的狀態。

檢測死鎖

????????運行時出現死鎖,能及時發現死鎖,把程序解脫出來

解除死鎖

????????發生死鎖后,解脫進程,通常撤銷進程,回收資源,再分配給正處于阻塞狀態的進程。

5)預防死鎖的方法

破壞請求和保持條件

協議1:

所有進程開始前,必須一次性地申請所需的所有資源,這樣運行期間就不會再提出資源要求,破壞了請求條件,即使有一種資源不能滿足需求,也不會給它分配正在空閑的資源,這樣它就沒有資源,就破壞了保持條件,從而預防死鎖的發生。

協議2:

允許一個進程只獲得初期的資源就開始運行,然后再把運行完的資源釋放出來。然后再請求新的資源。

破壞不可搶占條件

當一個已經保持了某種不可搶占資源的進程,提出新資源請求不能被滿足時,它必須釋放已經保持的所有資源,以后需要時再重新申請。

破壞循環等待條件

對系統中的所有資源類型進行線性排序,然后規定每個進程必須按序列號遞增的順序請求資源。假如進程請求到了一些序列號較高的資源,然后有請求一個序列較低的資源時,必須先釋放相同和更高序號的資源后才能申請低序號的資源。多個同類資源必須一起請求。

讀寫鎖

讀寫鎖概述

當有一個線程已經持有互斥鎖時,互斥鎖將所有試圖進入臨界區的線程都阻塞住。但是考慮一種情形,當前持有互斥鎖的線程只是要讀訪問共享資源,而同時有其它幾個線程也想讀取這個共享資源,但是由于互斥鎖的排它性,所有其它線程都無法獲取鎖,也就無法讀訪問共享資源了,但是實際上多個線程同時讀訪問共享資源并不會導致問題。

在對數據的讀寫操作中,更多的是讀操作,寫操作較少,例如對數據庫數據的讀寫應用。為了滿足當前能夠允許多個讀出,但只允許一個寫入的需求,線程提供了讀寫鎖來實現。

讀寫鎖的特點如下:

1)如果有其它線程讀數據,則允許其它線程執行讀操作,但不允許寫操作。

2)如果有其它線程寫數據,則其它線程都不允許讀、寫操作。

讀寫鎖分為讀鎖和寫鎖,規則如下:

1)如果某線程申請了讀鎖,其它線程可以再申請讀鎖,但不能申請寫鎖。

2)如果某線程申請了寫鎖,其它線程不能申請讀鎖,也不能申請寫鎖。

POSIX 定義的讀寫鎖的數據類型是: pthread_rwlock_t。

pthread_rwlock_init函數

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,

const pthread_rwlockattr_t *restrict attr);

功能:

用來初始化 rwlock 所指向的讀寫鎖。

參數:

rwlock:指向要初始化的讀寫鎖指針。

attr:讀寫鎖的屬性指針。如果 attr 為 NULL 則會使用默認的屬性初始化讀寫鎖,否則使用指定的 attr 初始化讀寫鎖。

可以使用宏 PTHREAD_RWLOCK_INITIALIZER 靜態初始化讀寫鎖,比如:

pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;

這種方法等價于使用 NULL 指定的 attr 參數調用 pthread_rwlock_init() 來完成動態初始化,不同之處在于PTHREAD_RWLOCK_INITIALIZER 宏不進行錯誤檢查。

返回值:

成功:0,讀寫鎖的狀態將成為已初始化和已解鎖。

失敗:非 0 錯誤碼。

pthread_rwlock_destroy函數

#include <pthread.h>

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

功能:

用于銷毀一個讀寫鎖,并釋放所有相關聯的資源(所謂的所有指的是由 pthread_rwlock_init() 自動申請的資源) 。

參數:

rwlock:讀寫鎖指針。

返回值:

成功:0

失敗:非 0 錯誤碼

pthread_rwlock_rdlock函數

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

功能:

以阻塞方式在讀寫鎖上獲取讀鎖(讀鎖定)。

如果沒有寫者持有該鎖,并且沒有寫者阻塞在該鎖上,則調用線程會獲取讀鎖。

如果調用線程未獲取讀鎖,則它將阻塞直到它獲取了該鎖。一個線程可以在一個讀寫鎖上多次執行讀鎖定。

線程可以成功調用 pthread_rwlock_rdlock() 函數 n 次,但是之后該線程必須調用 pthread_rwlock_unlock() 函數 n 次才能解除鎖定。

參數:

rwlock:讀寫鎖指針。

返回值:

成功:0

失敗:非 0 錯誤碼

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

  • 用于嘗試以非阻塞的方式來在讀寫鎖上獲取讀鎖。
  • 如果有任何的寫者持有該鎖或有寫者阻塞在該讀寫鎖上,則立即失敗返回。

pthread_rwlock_wrlock函數

#include <pthread.h>

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

功能:

在讀寫鎖上獲取寫鎖(寫鎖定)。

如果沒有寫者持有該鎖,并且沒有寫者讀者持有該鎖,則調用線程會獲取寫鎖。

如果調用線程未獲取寫鎖,則它將阻塞直到它獲取了該鎖。

參數:

rwlock:讀寫鎖指針。

返回值:

成功:0

失敗:非 0 錯誤碼

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

  • 用于嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖。
  • 如果有任何的讀者或寫者持有該鎖,則立即失敗返回。

pthread_rwlock_unlock函數

#include <pthread.h>

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

功能:

無論是讀鎖或寫鎖,都可以通過此函數解鎖。

參數:

rwlock:讀寫鎖指針。

返回值:

成功:0

失敗:非 0 錯誤碼

測試程序示例

下面是一個使用讀寫鎖來實現 4 個線程讀寫一段數據是實例。

在此示例程序中,共創建了 4 個線程,其中兩個線程用來寫入數據,兩個線程用來讀取數據。當某個線程讀操作時,其他線程允許讀操作,卻不允許寫操作;當某個線程寫操作時,其它線程都不允許讀或寫操作。

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  /*       *       創建了 4 個線程,其中兩個線程用來寫入數據,兩個線程用來讀取數據。*       當某個線程讀操作時,其他線程允許讀操作,卻不允許寫操作;當某個線程寫操作時,*       其它線程都不允許讀或寫操作。*/pthread_rwlock_t rwlock; //讀寫鎖
int num = 1;//讀操作,其他線程允許讀操作,卻不允許寫操作
void *fun1(void *arg)
{while (1){pthread_rwlock_rdlock(&rwlock);printf("read num first===%d\n", num);pthread_rwlock_unlock(&rwlock);sleep(1);}
}//讀操作,其他線程允許讀操作,卻不允許寫操作
void *fun2(void *arg)
{while (1){pthread_rwlock_rdlock(&rwlock);printf("read num second===%d\n", num);pthread_rwlock_unlock(&rwlock);sleep(2);}
}//寫操作,其它線程都不允許讀或寫操作
void *fun3(void *arg)
{while (1){pthread_rwlock_wrlock(&rwlock);num++;printf("write thread first\n");pthread_rwlock_unlock(&rwlock);sleep(2);}
}//寫操作,其它線程都不允許讀或寫操作
void *fun4(void *arg)
{while (1){pthread_rwlock_wrlock(&rwlock);num++;printf("write thread second\n");pthread_rwlock_unlock(&rwlock);sleep(1);}
}int main()
{pthread_t ptd1, ptd2, ptd3, ptd4;pthread_rwlock_init(&rwlock, NULL);//初始化一個讀寫鎖//創建線程pthread_create(&ptd1, NULL, fun1, NULL);pthread_create(&ptd2, NULL, fun2, NULL);pthread_create(&ptd3, NULL, fun3, NULL);pthread_create(&ptd4, NULL, fun4, NULL);//等待線程結束,回收其資源pthread_join(ptd1, NULL);pthread_join(ptd2, NULL);pthread_join(ptd3, NULL);pthread_join(ptd4, NULL);pthread_rwlock_destroy(&rwlock);//銷毀讀寫鎖return 0;
}

結果:

條件變量

條件變量概述

與互斥鎖不同,條件變量是用來等待而不是用來上鎖的,條件變量本身不是鎖

條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。

條件變量的兩個動作:

  • 條件不滿, 阻塞線程

  • 當條件滿足, 通知阻塞的線程開始工作

條件變量的類型: pthread_cond_t。

pthread_cond_init函數

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,

const pthread_condattr_t *restrict attr);

功能:

初始化一個條件變量

參數:

cond:指向要初始化的條件變量指針。

attr:條件變量屬性,通常為默認值,傳NULL即可

也可以使用靜態初始化的方法,初始化條件變量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

返回值:

成功:0

失敗:非0錯誤號

pthread_cond_destroy函數

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

功能:

銷毀一個條件變量

參數:

cond:指向要初始化的條件變量指針

返回值:

成功:0

失敗:非0錯誤號

pthread_cond_wait函數

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,

pthread_mutex_t *restrict mutex);

功能:

阻塞等待一個條件變量

a) 阻塞等待條件變量cond(參1)滿足

b) 釋放已掌握的互斥鎖(解鎖互斥量)相當于pthread_mutex_unlock(&mutex);

a) b) 兩步為一個原子操作。

c) 當被喚醒,pthread_cond_wait函數返回時,解除阻塞并重新申請獲取互斥鎖pthread_mutex_lock(&mutex);

參數:

cond:指向要初始化的條件變量指針

mutex:互斥鎖

返回值:

成功:0

失敗:非0錯誤號

限時等待一個條件變量:

int pthread_cond_timedwait(pthread_cond_t *restrict cond,

pthread_mutex_t *restrict mutex,

const struct?*restrict abstime);

功能:

限時等待一個條件變量

參數:

cond:指向要初始化的條件變量指針

mutex:互斥鎖

abstime:絕對時間

返回值:

? ?成功:0

? ?失敗:非0錯誤號

abstime補充說明:

struct timespec {

????????time_t tv_sec; /* seconds */ // 秒

????????long ? tv_nsec; /* nanosecondes*/ // 納秒

}

time_t cur = time(NULL); //獲取當前時間。

struct timespec t; //定義timespec 結構體變量t

t.tv_sec = cur + 1; // 定時1秒

pthread_cond_timedwait(&cond, &t);

pthread_cond_signal函數

喚醒至阻塞在條件變量上的線程

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

功能:

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

參數:

cond:指向要初始化的條件變量指針

返回值:

成功:0

失敗:非0錯誤號

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

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:

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

參數:

cond:指向要初始化的條件變量指針

返回值:

成功:0

失敗:非0錯誤號

生產者消費者條件變量模型

線程同步典型的案例即為生產者消費者模型,而借助條件變量來實現這一模型,是比較常見的一種方法。

假定有兩個線程,一個模擬生產者行為,一個模擬消費者行為。兩個線程同時操作一個共享資源(一般稱之為匯聚),生產向其中添加產品,消費者從中消費掉產品。

/*借助條件變量模擬 生產者-消費者 問題*/  
#include <stdlib.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <stdio.h>  /*鏈表作為公享數據,需被互斥量保護*/  
struct msg {  struct msg *next;  int num;  
};  struct msg *head;  /* 靜態初始化 一個條件變量 和 一個互斥量*/  
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;  
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;  void *consumer(void *p)  
{  struct msg *mp;  for (;;) {  pthread_mutex_lock(&lock);  while (head == NULL) {           //頭指針為空,說明沒有節點    可以為if嗎  pthread_cond_wait(&has_product, &lock);   // 解鎖,并阻塞等待  }  mp = head;        head = mp->next;                 //模擬消費掉一個產品  pthread_mutex_unlock(&lock);  printf("-Consume %lu---%d\n", pthread_self(), mp->num);  free(mp);  sleep(rand() % 5);  }  
}  void *producer(void *p)  
{  struct msg *mp;  for (;;) {  mp = malloc(sizeof(struct msg));  mp->num = rand() % 1000 + 1;        //模擬生產一個產品  printf("-Produce ---------------------%d\n", mp->num);  pthread_mutex_lock(&lock);  mp->next = head; //頭插法 head = mp;  pthread_mutex_unlock(&lock);  pthread_cond_signal(&has_product);  //將等待在該條件變量上的一個線程喚醒  sleep(rand() % 5);  }  
}  int main(int argc, char *argv[])  
{  pthread_t pid, cid;  srand(time(NULL));  pthread_create(&pid, NULL, producer, NULL);  pthread_create(&cid, NULL, consumer, NULL);  pthread_join(pid, NULL);  pthread_join(cid, NULL);  return 0;  
}  

結果:

一個生產者多個消費者模型

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  void err_thread(int ret, char *str)  
{  if (ret != 0) {  fprintf(stderr, "%s:%s\n", str, strerror(ret));  pthread_exit(NULL);  }  
}  struct msg {  int num;  struct msg *next;  
};  struct msg *head;  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 定義/初始化一個互斥量  
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定義/初始化一個條件變量  void *produser(void *arg)  
{  while (1) {  struct msg *mp = malloc(sizeof(struct msg));  mp->num = rand() % 1000 + 1;                        // 模擬生產一個數據`  printf("--produce %d\n", mp->num);  pthread_mutex_lock(&mutex);                         // 加鎖 互斥量  mp->next = head;                                    // 寫公共區域  head = mp;  pthread_mutex_unlock(&mutex);                       // 解鎖 互斥量  pthread_cond_signal(&has_data);    // 喚醒阻塞在條件變量 has_data上的線程.  sleep(rand() % 3);  }  return NULL;  
}  void *consumer(void *arg)  
{  while (1) {  struct msg *mp;  pthread_mutex_lock(&mutex);                         // 加鎖 互斥量  while (head == NULL) {  pthread_cond_wait(&has_data, &mutex);           // 阻塞等待條件變量, 解鎖  }                                  // pthread_cond_wait 返回時, 重寫加鎖 mutex  mp = head;  head = mp->next;  pthread_mutex_unlock(&mutex);                       // 解鎖 互斥量  printf("---------consumer id = %lu  :%d\n", pthread_self(), mp->num);  free(mp);  sleep(rand()%3);  }  return NULL;  
}  int main(int argc, char *argv[])  
{  int ret;  pthread_t pid, cid;  srand(time(NULL));  ret = pthread_create(&pid, NULL, produser, NULL);           // 生產者  if (ret != 0)   err_thread(ret, "pthread_create produser error");  ret = pthread_create(&cid, NULL, consumer, NULL);           // 消費者  if (ret != 0)   err_thread(ret, "pthread_create consuer error");  ret = pthread_create(&cid, NULL, consumer, NULL);           // 消費者  if (ret != 0)   err_thread(ret, "pthread_create consuer error");  ret = pthread_create(&cid, NULL, consumer, NULL);           // 消費者  if (ret != 0)   err_thread(ret, "pthread_create consuer error");  pthread_join(pid, NULL);  pthread_join(cid, NULL);  return 0;  
}  

結果:

條件變量的優缺點

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

如直接使用mutex,除了生產者、消費者之間要競爭互斥量以外,消費者之間也需要競爭互斥量,但如果匯聚(鏈表)中沒有數據,消費者之間競爭互斥鎖是無意義的。

有了條件變量機制以后,只有生產者完成生產,才會引起消費者之間的競爭。提高了程序效率。

信號量

信號量概述

信號量廣泛用于進程或線程間的同步和互斥,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。

編程時可根據操作信號量值的結果判斷是否對公共資源具有訪問的權限,當信號量值大于 0 時,則可以訪問,否則將阻塞。

PV 原語是對信號量的操作,一次 P 操作使信號量減1,一次 V 操作使信號量加1。

信號量主要用于進程或線程間的同步和互斥這兩種典型情況。

信號量數據類型為:sem_t。

信號量用于互斥:

信號量用于同步:

sem_init函數

初始化信號量:

#include <semaphore.h>

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

功能:

創建一個信號量并初始化它的值。一個無名信號量在被使用前必須先初始化。

參數:

sem:信號量的地址。

pshared:等于 0,信號量在線程間共享(常用);不等于0,信號量在進程間共享。

value:信號量的初始值。

返回值:

成功:0

失敗: - 1

sem_destroy函數

銷毀信號量:

#include <semaphore.h>

int sem_destroy(sem_t *sem);

功能:

刪除 sem 標識的信號量。

參數:

sem:信號量地址。

返回值:

成功:0

失敗: - 1

信號量P操作(減1)

int sem_wait(sem_t *sem):

#include <semaphore.h>

int sem_wait(sem_t *sem);

功能:

將信號量的值減 1。操作前,先檢查信號量(sem)的值是否為 0,若信號量為 0,此函數會阻塞,直到信號量大于 0 時才進行減 1 操作。

參數:

sem:信號量的地址。

返回值:

成功:0

失敗: - 1

int sem_trywait(sem_t *sem):

int sem_trywait(sem_t *sem);

  • 以非阻塞的方式來對信號量進行減 1 操作。
  • 若操作前,信號量的值等于 0,則對信號量的操作失敗,函數立即返回。

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout):

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

  • 限時嘗試將信號量的值減 1
  • abs_timeout:絕對時間

abs_timeout補充說明:

struct timespec {

????????time_t tv_sec; /* seconds */ // 秒

????????long ? tv_nsec; /* nanosecondes*/ // 納秒

}

time_t cur = time(NULL); //獲取當前時間。

struct timespec t; //定義timespec 結構體變量t

t.tv_sec = cur + 1; // 定時1秒

sem_timedwait(&cond, &t);

信號量V操作(加1)

#include <semaphore.h>

int sem_post(sem_t *sem);

功能:

將信號量的值加 1 并發出信號喚醒等待線程(sem_wait())。

參數:

sem:信號量的地址。

返回值:

成功:0

失敗:-1

獲取信號量的值

#include <semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);

功能:

獲取 sem 標識的信號量的值,保存在 sval 中。

參數:

sem:信號量地址。

sval:保存信號量值的地址。

返回值:

成功:0

失敗:-1

程序示例

/*信號量實現 生產者 消費者問題*/  #include <stdlib.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <stdio.h>  
#include <semaphore.h>  #define NUM 5                 int queue[NUM];                                     //全局數組實現環形隊列  
sem_t blank_number, product_number;                 //空格子信號量, 產品信號量  void *producer(void *arg)  
{  int i = 0;  while (1) {  sem_wait(&blank_number);                    //生產者將空格子數--,為0則阻塞等待  queue[i] = rand() % 1000 + 1;               //生產一個產品  printf("----Produce---%d\n", queue[i]);          sem_post(&product_number);                  //將產品數++  i = (i+1) % NUM;                            //借助下標實現環形  sleep(rand()%1);  }  
}  void *consumer(void *arg)  
{  int i = 0;  while (1) {  sem_wait(&product_number);                  //消費者將產品數--,為0則阻塞等待  printf("-Consume---%d\n", queue[i]);  queue[i] = 0;                               //消費一個產品   sem_post(&blank_number);                    //消費掉以后,將空格子數++  i = (i+1) % NUM;  sleep(rand()%3);  }  
}  int main(int argc, char *argv[])  
{  pthread_t pid, cid;  sem_init(&blank_number, 0, NUM);                //初始化空格子信號量為5, 線程間共享 -- 0  sem_init(&product_number, 0, 0);                //產品數為0  pthread_create(&pid, NULL, producer, NULL);  pthread_create(&cid, NULL, consumer, NULL);  pthread_join(pid, NULL);  pthread_join(cid, NULL);  sem_destroy(&blank_number);  sem_destroy(&product_number);  return 0;  
}  

結果:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/87685.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/87685.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/87685.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring 的IoC 和 AOP

第一部分&#xff1a;關于 IoC (控制反轉) 1. 核心思想 (What & Why) 首先&#xff0c;我會先解釋 IoC 的核心思想&#xff0c;而不是直接講技術。 “IoC&#xff0c;即控制反轉&#xff0c;它是一種重要的設計思想&#xff0c;而不是一個具體的技術。它的核心是將傳統上…

[實戰] Windows 文件讀寫函數 `ReadFile()` 和 `WriteFile()` 的阻塞與非阻塞操作詳解(含完整C語言示例)

Windows 文件讀寫函數 ReadFile() 和 WriteFile() 的阻塞與非阻塞操作詳解&#xff08;含完整C語言示例&#xff09; 在 Windows 平臺進行文件或設備&#xff08;如串口、管道&#xff09;編程時&#xff0c;ReadFile() 和 WriteFile() 是最常用的兩個 API 函數。它們既可以以…

Singularity 安裝

Singularity 是什么? 核心功能:用于創建/運行容器(將應用+依賴打包的獨立環境)。 與 Docker 的區別:專為 HPC(高性能計算)設計,無需后臺守護進程,支持非 root 運行容器(但安裝本身需 root 權限)。 適用于在具有 root 權限的計算機上從源代碼安裝 Singularity。…

辯證視角下 “辮子戲” 的文化反思與價值重構

前陣子播出的《人生若如初見》刻意美化晚清封建統治階級&#xff0c;淡化甚至掩蓋清政府閉關鎖國、喪權辱國、殘酷壓迫民眾等歷史真相&#xff0c;將本應批判反思的腐朽統治包裝成值得歌頌的對象&#xff1b;在歷史敘事上&#xff0c;或通過虛構、篡改重要歷史事件和人物形象&a…

MCP-server

&#x1f4a1; 說明&#xff1a;該模塊是 MCP 服務器的 數據中繼層&#xff0c;確保安全高效地從分布式來源獲取模型及其上下文&#xff0c;適用于邊緣計算和聯邦學習場景。若要查看完整代碼&#xff0c;建議直接訪問 GitHub 鏈接

第3講、LangChain性能優化:上下文緩存與流式響應實戰指南

目錄 概述上下文緩存優化流式響應優化復雜對話場景性能優化用戶體驗優化策略完整實現示例性能監控與調優總結 概述 在復雜對話場景中&#xff0c;大型語言模型面臨著響應延遲、重復計算、上下文管理等挑戰。本文將詳細介紹如何通過LangChain的上下文緩存和流式響應功能來優化…

http中GET和POST、PUT之間的區別

在HTTP協議中&#xff0c;GET、POST和PUT是三種最常用的請求方法&#xff0c;它們的主要區別如下&#xff1a; 1. GET 用途&#xff1a;用于請求資源&#xff08;查詢數據&#xff09;&#xff0c;不應修改服務器狀態。 參數傳遞&#xff1a;通過URL的查詢字符串&#xff08;…

埃夫特各種系列機器人運動學建模、軌跡規劃和工作空間求解

要求&#xff1a; 1.理論分析 1.1 正向運動學&#xff1a;根據D-H法完成機器人的正向運動學&#xff08;數學建模后基于Matlab計算公式&#xff09;&#xff1b; 1.2 工作空間分析&#xff1a;根據正向運動學結果&#xff0c;運用 MATLAB進行工作空間分析&#xff0c;完成工…

VUE3 路由的跳轉方法

Routerlink跳轉方法 name屬性對應了路由文件配置的name path屬性對應了路由的路徑 <RouterLink to"/login">點擊跳轉登陸</RouterLink> <RouterLink :to"{name:login}">點擊跳轉登陸</RouterLink> <RouterLink :to"{pat…

數據庫中間件ShardingSphere5

一、高性能架構模式 數據庫集群&#xff0c;第一種方式“讀寫分離”&#xff0c;第二種方式“數據庫分片”。 1.1 讀寫分離架構 讀寫分離原理&#xff1a;將數據庫讀寫操作分散到不同的節點上。 讀寫分離的基本實現&#xff1a; 主庫負責處理事務性的增刪改操作&#xff0c…

C++11 右值引用(Rvalue Reference)

在 C++11 中,右值引用(Rvalue Reference) 是一個革命性的語言特性,它為現代 C++ 的性能優化、資源管理以及語義清晰化奠定了基礎。通過引入 T&& 語法,C++11 支持了 移動語義(Move Semantics) 和 完美轉發(Perfect Forwarding),極大地提升了程序效率和代碼表達…

skynet源碼學習-skynet_main入口

skynet源碼學習-skynet_main入口 核心功能與啟動流程Shell腳本啟動示例main函數參數處理其他相關聯函數解析1. 配置加載器解析2. 環境變量設置3. 配置解析函數 核心配置項解析典型配置文件分析服務啟動與運行核心服務啟動流程完整啟動時序圖 核心功能與啟動流程 Skynet 的啟動…

前端圖文混排頁面一鍵導出PDF最佳實踐 —— 以Vue3+html2pdf.js為例

前言 在現代管理系統中,數據的歸檔、分享和線下流轉需求日益增長。如何將前端頁面的圖文內容高質量導出為PDF,成為許多企業和開發者關注的技術點。本文以實際項目為例,系統梳理前端導出PDF的完整實現思路與優化經驗。 一、項目背景與需求分析 1.1 背景故事 在某管理系統的…

19|Whisper+ChatGPT:請AI代你聽播客

今天&#xff0c;我們的課程開始進入一個新的主題了&#xff0c;那就是語音識別。過去幾周我們介紹的ChatGPT雖然很強大&#xff0c;但是只能接受文本的輸入。而在現實生活中&#xff0c;很多時候我們并不方便停下來打字。很多內容比如像播客也沒有文字版&#xff0c;所以這個時…

linux常用設置

1&#xff0c;ubuntu設置ssh-agent進入shell時自動加載 一&#xff0c;添加自動加載腳本&#xff0c;vim /etc/profile.d/keychain.sh # /etc/profile.d/keychain.sh # 自動啟動 ssh-agent 并加載多個私鑰 export KEYCHAIN_HOME"/root/.keychain" # 多個key&#xf…

電子電氣架構 --- 軟件供應商如何進入OEM體系

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 簡單,單純,喜歡獨處,獨來獨往,不易合同頻過著接地氣的生活,除了生存溫飽問題之外,沒有什么過多的欲望,表面看起來很高冷,內心熱情,如果你身…

破解數據可視化難題:帶軸斷裂的柱狀圖繪制全指南

引言&#xff1a;當數據跨度讓圖表失真時&#xff0c;軸斷裂技術如何力挽狂瀾&#xff1f; 在數據可視化的世界里&#xff0c;我們常常會遇到這樣的困境&#xff1a;一組數據中既有 "巨無霸" 般的極端值&#xff0c;又有需要精細展示的小數據。比如在財務報表中&…

以太網基礎①以太網相關通信接口

1. 今日摸魚任務 需要學習使用ZYNQ的以太網傳輸SCPI指令 需要把PL PS兩側的都用起來&#xff08;加油鴨&#xff01;&#xff09; 吶吶吶 今天就先學一下基礎知識唄 02_【邏輯教程】基于HDL的FPGA邏輯設計與驗證教程V3.5.2.pdf 51 以太網相關通信接口詳解 52 以太網&#xff…

FPGA基礎 -- Verilog 共享任務(task)和函數(function)

Verilog 中共享任務&#xff08;task&#xff09;和函數&#xff08;function&#xff09; 的詳細專業培訓&#xff0c;適合具有一定 RTL 編程經驗的工程師深入掌握。 一、任務&#xff08;task&#xff09;與函數&#xff08;function&#xff09;的基本區別 特性taskfunctio…

學習大模型---需要掌握的數學知識

1. 線性代數&#xff1a;樂高積木的世界 想象你有很多樂高積木塊。線性代數就是研究怎么用這些積木塊搭建東西&#xff0c;以及這些搭建好的東西有什么特性的學問。 向量&#xff1a; 就像一個有方向的箭頭&#xff0c;或者一組排好隊的數字。比如&#xff1a; 一個箭頭&…