什麼是互斥?什麼是同步?
互斥:一個資源一次隻能被一個通路者使用(保證通路資料,唯一通路性)
舉個例子:你去上廁所,門一開,人進去,門鎖上,在你上廁所期間别人不能打擾
同步:當多個通路者一起工作時并對每個通路者通路的時序有一定限制(保證通路資料,時序通路性)
舉個例子:你上完廁所,剛出來,本來應該由别人了,但是你此時又進去,你反複的進去出來,導緻别人都上不了廁所。是以同步就是保證時序性的,你出來了,應該去後面排隊。
為什麼要同步與互斥?
1.在運作多個任務,都需要通路同一種資源-----------競争
2.多個任務之間有依賴關系,某個任務的運作狀态依賴于另一個任務
同步互斥就是解決這類問題的。
互斥和同步的聯系?
同步是一種更為複雜的互斥,而互斥是一種特殊的同步。
也就是說互斥是兩個任務之間不可以同時運作,他們會互相排斥,必須等待一個執行完再執行,而同步也是不能同時運作,但是必須要按照某種次序來運作。但是互斥不限制任務的執行順序。(互斥任務是無序的,同步任務是有序的)
線程如何實作互斥
互斥量 :是一個可以處于兩态之一的變量,解鎖和加鎖。用來保護臨界資源的。當你通路臨界資源的時候,先要申請鎖,如果别人還沒釋放你就被阻塞,如果沒人用你就可以加鎖,這樣就很有效的保證臨界資源一段時間内隻能被一個人通路。
互斥量接口如下:
互斥量使用注意事項:
1.初始化互斥量(程式起始)
2.加鎖操作(進入臨界區之前)
3.解鎖操作(出臨界區之後)注意:你加鎖了,在有可能退出的地方都要解鎖,否則會出現死鎖問題
4.銷毀互斥量(程式結束前)
下面看一個例子,購票ticket--,不是一個原子操作,有可能在sleep的時候被切出去,造成錯誤判斷的問題,是以進入臨界區的時候要進行加鎖。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int ticket = 100;
pthread_mutex_t mutex;
void * get_ticket(void *arg)
{
int id = (int)arg;
while (1)
{
pthread_mutex_lock(&mutex);//進入臨界區加鎖
if (ticket>0)
{
usleep(100);
ticket--;
printf("thread :%d get a ticket :%d\n", id, ticket);
pthread_mutex_unlock(&mutex);//解鎖
}
else
{
pthread_mutex_unlock(&mutex);//在有可能退出的地方,都要解鎖,否則會造成死鎖不能退出
pthread_exit(NULL);
}
}
}
int main()
{
pthread_t tid[4];
pthread_mutex_init(&mutex, NULL);//初始化互斥鎖
int i, ret;
for (i = 0; i<4; i++)
{
ret = pthread_create(&tid[i], NULL, get_ticket, (void*)i);
if (ret != 0)
{
printf("pthread_creat error!!\n");
return -1;
}
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_join(tid[2], NULL);
pthread_join(tid[3], NULL);
pthread_mutex_destroy(&mutex);//銷毀互斥鎖
return 0;
}
線程如何實作同步
1.條件變量:是用來等待某一條件的發生
主要包括以下兩個動作:
等待“條件變量的條件成立”而挂起
使“條件成立”(給出條件成立信号)
為了防止競争,條件變量一般和互斥量搭配使用
怎樣了解:1.條件變量也應當受到保護是以要使用互斥鎖(線程在改變條件變量狀态之前先上鎖)2.如果隻使用互斥鎖,其他線程到這個程式上來就會判斷是否上鎖,如果上鎖了就等待,這個時候可能會有很多線程都來判斷等待,然後阻塞在這裡,當調用鎖的線程釋放鎖後,那麼被阻塞的線程又會來搶奪這個資源。而加上條件變量,條件不滿足可以讓阻塞的線程等待在條件變量上,條件滿足會喚醒等待在條件變量上的線程,進而彌補了互斥量的不足。(總結條件變量與互斥量一起使用,允許線程以無競争的方式等待條件的發生)
條件變量相關接口:
生産者消費者模型的實作:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
int goods = 0;
pthread_cond_t full;//生産
pthread_cond_t empty;//消費
pthread_mutex_t mutex;
void *consumer(void *arg)
{
int id = (int)arg;
//我是一個消費者
while(1) {
pthread_mutex_lock(&mutex);
while(goods== 0) {
pthread_cond_wait(&empty, &mutex);//pthread_con_wait幹兩件事:原子操作 1. 釋放鎖 2.等待
}
usleep(100000);
goods= 0;
printf("consumer %d get an apple!!\n", id);
pthread_cond_signal(&full);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *producer(void *arg)
{
int id = (int)arg;
while(1) {
pthread_mutex_lock(&mutex);
while(goods== 1) {//思考:此處為什麼不是if判斷,而是while 原因:因為在signal喚醒wait之間,有另一個生産者可能已經生産了,是以此時應繼續判斷
pthread_cond_wait(&full, &mutex);
}
goods = 1;
printf("producer %d put an apple!!\n", id);
//喚醒等待在cond上的線程
pthread_cond_signal(&empty);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_t tid;
int ret, i;
pthread_cond_init(&full, NULL);
pthread_cond_init(&empty, NULL);
pthread_mutex_init(&mutex, NULL);
for (i = 0; i < 4; i++) {
ret = pthread_create(&tid, NULL, producer, (void*)i);
}
for (i = 0; i < 4; i++) {
ret = pthread_create(&tid, NULL, consumer, (void*)i);
}
pthread_join(tid, NULL);
pthread_cond_destroy(&empty);
pthread_cond_destroy(&full);
pthread_mutex_destroy(&mutex);
return 0;
}
2.POSIX信号量:有等待隊列的計數器(記錄臨界資源的個數)
目的:為了同步操作,達到無沖突的通路共享資源的目的POSIX
POSIX信号量接口如下:
生産者消費者代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
sem_t producer, consumer;
int noodles = 1;
void* thr_producer(void* arg)
{
while (1)
{
sem_wait(&producer);
printf("producer put a noodles!!!\n");
noodles=1;
sleep(1);
sem_post(&consumer);
}
return NULL;
}
void* thr_consumer(void* arg)
{
while (1)
{
sem_wait(&consumer);
printf("consumer get a noodles!!!\n");
noodles=0;
sem_post(&producer);
}
return NULL;
}
int main()
{
int ret;
pthread_t tid1, tid2;
ret = sem_init(&producer, 0, 0);
ret = sem_init(&consumer, 0, 1);
if (ret<0)
{
printf("sem init error!!\n");
return -1;
}
ret == pthread_create(&tid1, NULL, thr_producer, NULL);
if (ret != 0)
{
printf("pthread1 create error!!\n");
return -1;
}
ret == pthread_create(&tid2, NULL, thr_consumer, NULL);
if (ret != 0)
{
printf("pthread2 create error!!\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&producer);
sem_destroy(&consumer);
return 0;
}
(3)讀寫鎖
在一些多線程場景下,有些公共的資料修改的機會比較少,相比較修改,我們讀的機會反而更高,為這段代碼加鎖,會極大的降低我們程式的效率,而讀寫鎖就是專門來處理這種問題的。
讀寫鎖特點:寫獨占,讀共享
讀寫鎖其實也是一種自旋鎖,擷取不到鎖,我不挂起,頻頻回頭,一直嘗試加鎖(自旋鎖應用于你确定加鎖不會耗費很長的時間)
讀寫鎖的接口如下:
讀寫者模型:三種關系,兩種角色,一個交易場所 (我們考慮讀者優先)
讀者與讀者 : 共享(也可以說沒啥關系)
讀者與寫者 : 互斥與同步(讀者寫着不能同時進行,一旦有寫者,則後續讀者将會等待,喚醒時優先考慮讀者)
寫者與寫者 :互斥
代碼實作如下:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
char* ptr = "i am blockboard!!!";
pthread_rwlock_t rwlock;
void* thr_write(void * arg)
{
while (1)
{
pthread_rwlock_wrlock(&rwlock);
printf("draw ....\n");
sleep(1);
pthread_rwlock_wrlock(&rwlock);
usleep(1000);
}
return NULL;
}
void* thr_read(void *arg)
{
int id = (int)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
printf("i am:%d public~~,[%s] is beautiful!!\n", id, ptr);
sleep(1);
printf("wake up%d!!\n", id);
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
return NULL;
}
int main()
{
pthread_t tid1, tid2, tid3;
int ret;
pthread_rwlock_init(&rwlock, NULL);
ret = pthread_create(&tid1, NULL, thr_write, (void*)1);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
ret = pthread_create(&tid3, NULL, thr_read, (void*)3);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
ret = pthread_create(&tid2, NULL, thr_read, (void*)2);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}