一、互斥量
1、為什么要使用互斥量
當(dāng)多個(gè)線程共享相同的內(nèi)存時(shí),需要每一個(gè)線程看到相同的視圖。當(dāng)一個(gè)線程修改變量時(shí),而其他線程也可以讀取或者修改這個(gè)變量,就需要對(duì)這些線程同步,確保他們不會(huì)訪問到無效的變量。
在變量修改時(shí)間多于一個(gè)存儲(chǔ)器訪問周期的處理器結(jié)構(gòu)中,當(dāng)存儲(chǔ)器的讀和寫這兩個(gè)周期交叉時(shí),這種潛在的不一致性就會(huì)出現(xiàn)。當(dāng)然這與處理器相關(guān),但是在可移植的程序中并不能對(duì)處理器做出任何假設(shè)。
2、互斥鎖的初始化和銷毀
為了讓線程訪問數(shù)據(jù)不產(chǎn)生沖突,這要就需要對(duì)變量加鎖,使得同一時(shí)刻只有一個(gè)線程可以訪問變量。互斥量本質(zhì)就是鎖,訪問共享資源前對(duì)互斥量加鎖,訪問完成后解鎖
當(dāng)互斥量加鎖以后,其他所有需要訪問該互斥量的線程都將阻塞
當(dāng)互斥量解鎖以后,所有因?yàn)檫@個(gè)互斥量阻塞的線程都將變?yōu)榫途w態(tài),第一個(gè)獲得cpu的線程會(huì)獲得互斥量,變?yōu)檫\(yùn)行態(tài),而其他線程會(huì)繼續(xù)變?yōu)樽枞谶@種方式下訪問互斥量每次只有一個(gè)線程能向前執(zhí)行
互斥量用pthread_mutex_t類型的數(shù)據(jù)表示,在使用之前需要對(duì)互斥量初始化
1、如果是動(dòng)態(tài)分配的互斥量,可以調(diào)用pthread_mutex_init()函數(shù)初始化
2、如果是靜態(tài)分配的互斥量,還可以把它置為常量PTHREAD_MUTEX_INITIALIZER
3、動(dòng)態(tài)分配的互斥量在釋放內(nèi)存之前需要調(diào)用pthread_mutex_destroy()
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3、加鎖和解鎖
加鎖:
int pthread_mutex_lock(pthread_mutex_t *mutex);
成功返回0,失敗返回錯(cuò)誤碼。如果互斥量已經(jīng)被鎖住,那么會(huì)導(dǎo)致該線程阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功返回0,失敗返回錯(cuò)誤碼。如果互斥量已經(jīng)被鎖住,不會(huì)導(dǎo)致線程阻塞
解鎖:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,失敗返回錯(cuò)誤碼。如果一個(gè)互斥量沒有被鎖住,那么解鎖就會(huì)出錯(cuò)
二、讀寫鎖
1、什么是讀寫鎖?
讀寫鎖與互斥量類似,不過讀寫鎖有更高的并行性。互斥量要么加鎖要么不加鎖,而且同一時(shí)刻只允許一個(gè)線程對(duì)其加鎖。對(duì)于一個(gè)變量的讀取,完全可以讓多個(gè)線程同時(shí)進(jìn)行操作。
pthread_rwlock_t rwlock
讀寫鎖有三種狀態(tài),讀模式下加鎖,寫模式下加鎖,不加鎖。一次只有一個(gè)線程可以占有寫模式下的讀寫鎖,但是多個(gè)線程可以同時(shí)占有讀模式的讀寫鎖
讀寫鎖在寫加鎖狀態(tài)時(shí),在它被解鎖之前,所有試圖對(duì)這個(gè)鎖加鎖的線程都會(huì)阻塞。讀寫鎖在讀加鎖狀態(tài)時(shí),所有試圖以讀模式對(duì)其加鎖的線程都會(huì)獲得訪問權(quán),但是如果線程希望以寫模式對(duì)其加鎖,它必須阻塞直到所有的線程釋放鎖。
當(dāng)讀寫鎖以讀模式加鎖時(shí),如果有線程試圖以寫模式對(duì)其加鎖,那么讀寫鎖會(huì)阻塞隨后的讀模式鎖請(qǐng)求。這樣可以避免讀鎖長期占用,而寫鎖達(dá)不到請(qǐng)求。
讀寫鎖非常適合對(duì)數(shù)據(jù)結(jié)構(gòu)讀次數(shù)大于寫次數(shù)的程序,當(dāng)它以讀模式鎖住時(shí),是以共享的方式鎖住的;當(dāng)它以寫模式鎖住時(shí),是以獨(dú)占的模式鎖住的。
2、讀寫鎖的初始化和銷毀
讀寫鎖在使用之前必須初始化:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
使用完需要銷毀:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功返回0 ,失敗返回錯(cuò)誤碼
3、加鎖和解鎖
讀模式加鎖:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
寫模式加鎖:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
解鎖:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功返回0,錯(cuò)誤返回錯(cuò)誤碼。
三、條件變量
1、條件變量的引入
一個(gè)典型的實(shí)例:
在一條生產(chǎn)先線上有一個(gè)倉庫,當(dāng)生產(chǎn)者生產(chǎn)的時(shí)候需要鎖住倉庫獨(dú)占,而消費(fèi)者取產(chǎn)品的時(shí)候也要鎖住倉庫獨(dú)占。如果生產(chǎn)者發(fā)現(xiàn)倉庫滿了,那么他就不能生產(chǎn)了,變成了阻塞狀態(tài)。但是此時(shí)由于生產(chǎn)者獨(dú)占倉庫,消費(fèi)者又無法進(jìn)入倉庫去消耗產(chǎn)品,這樣就造成了一個(gè)僵死狀態(tài)。
我們需要一種機(jī)制,當(dāng)互斥量被鎖住以后發(fā)現(xiàn)當(dāng)前線程還是無法完成自己的操作,那么它應(yīng)該釋放互斥量,讓其他線程工作。
- 可以采用輪詢的方式,不停的查詢你需要的條件
- 讓系統(tǒng)來幫你查詢條件,使用條件變量
pthread_cond_t cond
2、條件變量的初始化和銷毀
靜態(tài)初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
動(dòng)態(tài)初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
默認(rèn)屬性為空NULL
條件變量使用完成之后需要銷毀
int pthread_cond_destroy(pthread_cond_t *cond);
3、條件變量的使用
條件變量使用需要配合互斥量使用:
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
1、使用pthread_cond_wait
等待條件變?yōu)檎妗鬟f給pthread_cond_wait
的互斥量對(duì)條件進(jìn)行保護(hù),調(diào)用者把鎖住的互斥量傳遞給函數(shù)。
2、這個(gè)函數(shù)將線程放到等待條件的線程列表上,然后對(duì)互斥量進(jìn)行解鎖,這是個(gè)原子操作。
當(dāng)條件滿足時(shí)這個(gè)函數(shù)返回,返回以后繼續(xù)對(duì)互斥量加鎖。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
這個(gè)函數(shù)與pthread_cond_wait
類似,只是多一個(gè)timeout
,如果到了指定的時(shí)間條件還不滿足,那么就返回。
時(shí)間用下面的結(jié)構(gòu)體表示:
struct timespec{
time_t tv_sec;
long tv_nsec;
};
注意,這個(gè)時(shí)間是絕對(duì)時(shí)間。例如你要等待3分鐘,就要把當(dāng)前時(shí)間加上3分鐘然后轉(zhuǎn)換到timespec
,而不是直接將3分鐘轉(zhuǎn)換到timespec
。
當(dāng)條件滿足的時(shí)候,需要喚醒等待條件的線程:
int pthread_cond_broadcast(pthread_cond_t *cond);
// pthread_cond_broadcast喚醒等待條件的所有線程
int pthread_cond_signal(pthread_cond_t *cond);
// pthread_cond_signal至少喚醒等待條件的某一個(gè)線程
注意,一定要在條件改變以后再喚醒線程。
典型例子:生產(chǎn)者與消費(fèi)者。
/*
*DESCRIPTION: 生產(chǎn)者與消費(fèi)者問題
*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#define BUFFER_SIZE 5 //產(chǎn)品庫存大小
#define PRODUCT_CNT 30 //產(chǎn)品生產(chǎn)總數(shù)
struct product_cons
{
int buffer[BUFFER_SIZE]; //生產(chǎn)產(chǎn)品值
pthread_mutex_t lock; //互斥鎖 volatile int
int readpos, writepos; //讀寫位置
pthread_cond_t notempty; //條件變量,非空
pthread_cond_t notfull; //非滿
}buffer;
void init(struct product_cons *p)
{
pthread_mutex_init(&p->lock, NULL); //互斥鎖
pthread_cond_init(&p->notempty, NULL); //條件變量
pthread_cond_init(&p->notfull, NULL); //條件變量
p->readpos = 0; //讀寫位置
p->writepos = 0;
}
void finish(struct product_cons *p)
{
pthread_mutex_destroy(&p->lock); //互斥鎖
pthread_cond_destroy(&p->notempty); //條件變量
pthread_cond_destroy(&p->notfull); //條件變量
p->readpos = 0; //讀寫位置
p->writepos = 0;
}
//存儲(chǔ) 一個(gè)數(shù)據(jù) 到 bufferr
void put(struct product_cons *p, int data) //輸入產(chǎn)品子函數(shù)
{
pthread_mutex_lock(&p->lock);
if((p->writepos+1)%BUFFER_SIZE == p->readpos)
{
printf("producer wait for not full\n");
pthread_cond_wait(&p->notfull, &p->lock);
}
p->buffer[p->writepos] = data;
p->writepos ++;
if(p->writepos >= BUFFER_SIZE)
p->writepos = 0;
pthread_cond_signal(&p->notempty);
pthread_mutex_unlock(&p->lock);
}
//讀,移除 一個(gè)數(shù)據(jù) 從 buffer
int get(struct product_cons *p)
{
int data;
pthread_mutex_lock(&p->lock);
if(p->readpos == p->writepos)
{
printf("consumer wait for not empty\n");
pthread_cond_wait(&p->notempty, &p->lock);
}
data = p->buffer[p->readpos];
p->readpos++;
if(p->readpos >= BUFFER_SIZE)
p->readpos = 0;
pthread_cond_signal(&p->notfull);
pthread_mutex_unlock(&p->lock);
return data;
}
void *producer(void *data) //子線程 ,生產(chǎn)
{
int n;
for(n = 1; n <= 50; ++n) //生產(chǎn) 50 個(gè)產(chǎn)品
{
sleep(1);
printf("put the %d product ...\n", n);
put(&buffer,n);
printf("put the %d product success\n", n);
}
printf("producer stopped\n");
return NULL;
}
void *consumer(void *data)
{
static int cnt = 0;
int num;
while(1)
{
sleep(2);
printf("get product ...\n");
num = get(&buffer);
printf("get the %d product success\n", num);
if(++cnt == PRODUCT_CNT)
break;
}
printf("consumer stopped\n");
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t th_a,th_b;
void *retval;
init(&buffer);
pthread_create(&th_a, NULL, producer, 0);
pthread_create(&th_b, NULL, consumer, 0);
pthread_join(th_a, &retval); // 等待新線程的結(jié)束
pthread_join(th_b, &retval);
finish(&buffer);
return 0;
}