Linux下執行緒的同步與互斥
- 執行緒間通信
- 同步
- 信號量
- P操作(P(S)):
- V操作(V(S)):
- Posix信號量
- pthread庫常用的函式
- sem_init函式
- P操作和V操作的函式
- 執行緒同步的示例
- 示例(生產者/消費者問題)
- 互斥
- 臨界資源
- 臨界區
- 互斥機制
- pthread提供的函式
- 互斥鎖初始化函式——pthread_mutex_init
- 申請鎖函式——pthread_mutex_lock
- 釋放鎖函式——pthread_mutex_unlock
- 執行緒互斥示例
執行緒間通信
執行緒共享同一行程的地址空間
優點:
- 通過全域變數交換資料
缺點:
- 多個執行緒訪問共享全域資料時需要同步或者互斥
同步
同步指的是多個任務按照約定的先后次序互相配合完成一件事情,
1968年,Edsgar Dijkstra基于信號量的概念提出了一種同步機制,由信號量來決定執行緒是繼續運行還是阻塞等待,
信號量
- 信號量代表某一類資源,其值表示系統中該資源的數量,
- 信號量是一個受保護的變數,只能通過三種操作來訪問,
> 初始化
> P操作(申請資源)
> V操作(釋放資源)
P操作(P(S)):
if(信號量的值大于0){
申請資源的任務繼續運行;
信號量的值減一;
}
else{
申請資源的任務阻塞;
}
V操作(V(S)):
信號量的值加一;
if(有任務在等待資源){
喚醒等待的任務,讓其繼續運行;
}
Posix信號量
-
Posix中定義了兩種信號量:
>無名信號量(基于記憶體的信號量)
>有名信號量
下面將介紹無名信號量,
pthread庫常用的函式
sem_init函式
#include <semaphore.h>
int sem_init(sem_t* sem,int pshared,unsigned int val);
- 函式呼叫成功時回傳0,失敗時回傳EOF
- 第一個引數(sem):指向要初始化的信號量物件
- 第二個引數(pshared): 0——執行緒間 1——行程間
- 第三個引數(val):信號量初值
P操作和V操作的函式
#include <semaphore.h>
int sem_wait(sem_t* sem); //P操作
int sem_post(sem_t* sem); //V操作
- 函式呼叫成功時回傳0,失敗時回傳EOF
- 引數(sem):指向要操作的信號量物件
執行緒同步的示例
示例(生產者/消費者問題)
兩個執行緒同步讀寫緩沖區
這個問題需要兩個信號量實作讀寫的同步,一個信號量來描述可讀緩沖區的數量,一個來描述可寫緩沖區的數量,
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>
char buf[32];
sem_t r;
sem_t w;
void* function(void* arg);
int main(void)
{
pthread_t a_thread;
if(sem_init(&r,0,0)<0) //創建可讀緩沖區信號量,初始為0
{
perror("sem_init");
exit(-1);
}
if(sem_init(&w,0,1)<0) //創建可寫緩沖區信號量
{
perror("sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)!=0)
{
printf("fail to pthread_create");
exit(-1);
}
printf("input 'quit' to exit\n");
do
{
sem_wait(&w);
fgets(buf,32,stdin);
sem_post(&r);
}while(strncmp(buf,"quit",4)!=0);
return 0;
}
void* function(void* arg)
{
while(1)
{
sem_wait(&r);
printf("you enter %d characters\n",strlen(buf));
sem_post(&w);
}
}
結果為

在寫這段代碼時,首先要主要到,創建信號量應在創建執行緒之前完成,防止出現還未創建信號量主執行緒的CPU時間片用完導致執行了新的執行緒,發生訪問沖突,
互斥
臨界資源
- 一次只允許一個任務(行程、執行緒)訪問的共享資源
臨界區
- 訪問臨界區的代碼
互斥機制
- mutex互斥鎖
- 任務訪問臨界資源之前申請鎖,訪問完后釋放鎖
pthread提供的函式
互斥鎖初始化函式——pthread_mutex_init
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態初始化互斥鎖
- 函式呼叫成功時回傳0,失敗時回傳錯誤碼
- 第一個引數(mutex):指向要初始化的互斥鎖物件
- 第二個引數(attr):互斥鎖屬性,NULL表示默認屬性
或者你可以使用宏來靜態初始化鎖變數,
互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實作中僅有一個鎖型別屬性,不同的鎖型別在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同,當前有四個值可供選擇:
enum
{
PTHREAD_MUTEX_TIMED_NP,
PTHREAD_MUTEX_RECURSIVE_NP,
PTHREAD_MUTEX_ERRORCHECK_NP,
PTHREAD_MUTEX_ADAPTIVE_NP
};
- PTHREAD_MUTEX_TIMED_NP,這是默認值,也就是普通鎖,當一個執行緒加鎖以后,其余請求鎖的執行緒將形成一個等待佇列,并在解鎖后按優先級獲得鎖,這種鎖策略保證了資源分配的公平性,
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個執行緒對同一個鎖成功獲得多次,并通過多次unlock解鎖,如果是不同執行緒請求,則在加鎖執行緒解鎖時重新競爭,
- PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個執行緒請求同一個鎖,則回傳EDEADLK(死鎖),否則與PTHREAD_MUTEX_TIMED_NP型別動作相同,這樣保證當不允許多次加鎖時不出現最簡單情況下的死鎖,
- PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖型別,僅等待解鎖后重新競爭,
通常我們使用默認的普通鎖
申請鎖函式——pthread_mutex_lock
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 函式呼叫成功時回傳0,失敗時回傳錯誤碼
- mutex引數:指向要初始化的互斥鎖物件
- 如果無法獲得鎖,任務阻塞
釋放鎖函式——pthread_mutex_unlock
int
__pthread_mutex_unlock (pthread_mutex_t *mutex)
- 函式呼叫成功時回傳0,失敗時回傳錯誤碼
- mutex引數:指向要初始化的互斥鎖物件
- 執行完臨界區要及時釋放鎖
執行緒互斥示例
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>
unsigned int count,value1,value2;
pthread_mutex_t lock;
void* function(void* arg);
int main(void)
{
pthread_t a_thread;
if(pthread_mutex_init(&lock,NULL)!=0)
{
printf("fail to pthread_mutex_init\n");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)!=0)
{
printf("fail to pthread_create\n");
exit(-1);
}
while(1)
{
count++;
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return 0;
}
void* function(void* arg)
{
while(1)
{
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
if(value1 != value2)
{
printf("value1 = %u, value2 = %u\n",value1,value2);
usleep(100000);
}
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return NULL;
}
首先我們看不加鎖的執行情況,我們可以發現會出現value1與value2不同情況,

枷鎖之后的結果

我們可以分析出如果不加鎖,那么當一個行程在執行value1 = count;就可能發生時間片到執子執行緒,而此時可能value2還未被賦值,所以value1!=value2,
這時我們使用互斥鎖便能很好的解決同時訪問臨界區的問題,
如果你看到這覺得寫得不錯,那么可以幫我點一個贊再走吧^.^
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/266303.html
標籤:其他
上一篇:音視頻小白入門
