線程同步
當我們多線程訪問同一個臨界資源時,會造成并發訪問一個臨界資源,使得臨界資源數據不安全,我們引入了鎖的概念,解決了臨界資源訪問不安全的情況,對于線程而言競爭鎖的能力有強有弱,對于之前就搶到鎖的線程,當他釋放鎖后,由于不用做什么準備工作,他競爭鎖的能力很強,導致這個線程反復的爭奪鎖,來訪問臨界資源,導致其他線程處于饑餓狀態
同步:同步問題是保證數據安全的情況下,讓我們的線程具有一定的順序性
解決方案
條件變量的引入
當多線程來訪問臨界資源時,首先不會讓他去訪問臨界資源,而是將這個線程放入條件變量維護的隊列中去,等待臨界資源就緒,舉個例子,一個幼兒園里面,到了飯點,而小朋友們是每一個線程,而飯就是臨界資源,每次只能有一個孩子在餐廳里面打飯,這就是鎖,每個孩子跑步速度不一樣,競爭鎖的能力不一樣,為了避免一個孩子一直在餐廳不走,一直吃飯,幼兒園老師做了這個規定,每個小朋友吃飯必須去排隊,剛吃完的孩子還想吃的話,必須排隊在隊的后面。而條件變量維護的隊列類比與排隊,每個線程訪問完臨界資源之后必須在條件變量維護的隊列后面排隊
條件變量接口介紹
1、主要應用函數:
pthread_cond_init()函數
功能:初始化一個條件變量
pthread_cond_wait()函數
功能:阻塞等待一個條件變量
pthread_cond_signal()函數
功能:喚醒至少一個阻塞在條件變量上的線程
pthread_cond_broadcast()函數
功能:喚醒全部阻塞在條件變量上的線程
pthread_cond_destroy()函數
功能:銷毀一個條件變量
函數分析
1.初始化一個條件變量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
參2:attr表條件變量屬性,通常為默認值,傳NULL即可
也可以使用靜態初始化的方法,初始化條件變量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.阻塞等待一個條件變量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函數作用:
阻塞等待條件變量cond(參1)滿足
釋放已掌握的互斥鎖(解鎖互斥量)相當于pthread_mutex_unlock(&mutex);
當被喚醒,pthread_cond_wait函數返回時,解除阻塞并重新申請獲取互斥鎖pthread_mutex_lock(&mutex);
3.喚醒至少一個阻塞在條件變量上的線程
int pthread_cond_signal(pthread_cond_t *cond);
4.喚醒全部阻塞在條件變量上的線程
int pthread_cond_broadcast(pthread_cond_t *cond);
5.銷毀一個條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
代碼實現線程同步
1.makefile編寫
mycond:mycond.ccg++ -o mycond mycond.cc -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mycond
2.mycond.cc
#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;int cnt=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;void*fun(void*args)
{pthread_detach(pthread_self());uint64_t num=(uint64_t)args;cout<<"pthread:"<<num<<"create success"<<endl;while(1){ pthread_mutex_lock(&mutex);cout<<"pthread:"<<num<<",cnt:"<<cnt++<<endl;pthread_mutex_unlock(&mutex);}}
int main()
{for( uint64_t i=0;i<5;i++)
{pthread_t tid;pthread_create(&tid,nullptr,fun,(void*)i);
}
while(1)
{}
}
代碼解釋:
初始化一個鎖,一個條件變量,臨界資源cnt,每個線程在自己要執行的fun函數內,需要訪問臨界資源cnt,對cnt++,在主函數中創建5個線程,為了防止主線程退出,所有線程都退出,所以讓主線程死循環,在每個fun函數里面實現線程分離,不需要主線程來等待回收其他線程,讓操作系統自己回收
由于線程2競爭鎖的能力強,每次都是線程2來訪問臨界資源。
為了解決一個線程競爭鎖的能力強,使用線程同步,先實現代碼在來解釋
#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;int cnt=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;void*fun(void*args)
{pthread_detach(pthread_self());uint64_t num=(uint64_t)args;cout<<"pthread:"<<num<<"create success"<<endl;while(1){ pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);//新增行cout<<"pthread:"<<num<<",cnt:"<<cnt++<<endl;pthread_mutex_unlock(&mutex);}}
int main()
{for( uint64_t i=0;i<5;i++)
{pthread_t tid;pthread_create(&tid,nullptr,fun,(void*)i);
}
while(1)
{
sleep(1);//新增行
pthread_cond_signal(&cond);//新增行
cout<<"signal one thread..."<<endl; //新增行
}
}
pthread_cond_wait函數可以將剛申請鎖的線程讓其加入隊列,讓其休眠,該函數還會讓對應的線程釋放鎖,這樣一輪下來,所有想訪問臨界資源的線程都會出現在條件變量維護的隊列中,等待喚醒,一次喚醒一個,讓他們去訪問臨界資源,在主線程中來喚醒在維護條件變量對應的隊列中其他線程,去訪問臨界資源,sleep作用防止打印太快看不到效果。
五個線程按照順序去訪問臨界資源
cp問題(生產消費者模型)
此時有三種關系:
生產者和生產者:互斥
消費者和生產者:互斥,同步(一個放,一個拿,肯定要保證順序問題)
消費者和消費者:互斥
3種關系:生產者和生產者,消費者和消費者,生產者和消費者
2種角色:生產者和消費者
1個交易場所:特定結構的內存空間
特點:支持忙閑不均(對比馮諾依曼結構)
生產和消費進行解耦
代碼實現生產消費者模型
makefile實現
cp:cp.ccg++ -o cp cp.cc -std=c++11 -lpthread
.PHONY:clean
clean:rm -f cp
cp.cc(未完成)
#include<iostream>
#include<pthread.h>
#include"blockqueue.hpp"
#include<unistd.h>using namespace std;
void*consumer(void*args)
{
Blockqueue<int>*cq=static_cast<Blockqueue<int>*>(args);
while(1)
{
//消費數據,將隊列中的數據pop
int data=cq->pop();
cout<<"消費了一個數據:"<<data<<endl;
}
}
void* productor(void*args)
{int data=0;
Blockqueue<int>*pq=static_cast<Blockqueue<int>*>(args);
while(1)
{ sleep(1);//生產數據放到隊列中去pq->push(data);cout<<"生產了一個數據:"<< data++ <<endl;
}}int main()
{
pthread_t p,c;
Blockqueue<int>*st=new Blockqueue<int>();pthread_create(&p,nullptr,productor,st);pthread_create(&c,nullptr,consumer,st);pthread_join(p,nullptr);
pthread_join(c,nullptr);//線程等待回收
delete st;
return 0;}
主線程創建兩個線程,一個是生產者,一個是消費者,生產者將數據插入在阻塞隊列中,消費者將數據取出阻塞隊列中的數據,阻塞隊列相當于臨界資源,主線程中new一個阻塞隊列對象作為共享的資源,在各個線程要執行的函數里面,pthread_create最后一個參數可以是資源的起始地址,我們可以傳阻塞隊列類對象過去,讓生產者線程和消費者線程都可以訪問到
blockqueue.hpp
#pragma once
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;template<class T>
class Blockqueue
{static const int defalutnum=5;
public:
Blockqueue(int maxcap=defalutnum)
:maxcap_(maxcap){pthread_mutex_init(&mutex_,nullptr);pthread_cond_init(&p_cond_,nullptr);pthread_cond_init(&c_cond_,nullptr);}
T pop()
{T out=q_.front();q_.pop();return out;
}
void push(const T&in)
{
q_.push(in);
}
~Blockqueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(& p_cond_);pthread_cond_destroy(& c_cond_);}
private:
queue<T>q_;
int maxcap_;
pthread_mutex_t mutex_;
pthread_cond_t p_cond_;
pthread_cond_t c_cond_;};
由于該阻塞隊列是兩者共享的,臨界資源,防止并發訪問,要實現安全保護,所以要使用鎖,因為生產者和消費者都會對臨界資源訪問,兩者對鎖的競爭能力不一樣,可能會導致饑餓問題,使用線程同步,所以要用條件變量
maxcap為隊列最大可以放幾個值,這是我們規定的,為了解決忙而不均,同步問題,達到最大,就讓生產者線程休眠,如果阻塞隊列的個數為0,就讓消費者線程休眠,所以這里需要兩個條件變量分別維護。
現在要解決的是臨界資源的保護,保證多線程并發安全,使用鎖,還要就是兩個線程競爭鎖的能力不同,可能導致另一個線程饑餓問題,使用線程同步
blockqueue.hpp
#pragma once
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;template<class T>
class Blockqueue
{static const int defalutnum=5;
public:
Blockqueue(int maxcap=defalutnum)
:maxcap_(maxcap){pthread_mutex_init(&mutex_,nullptr);pthread_cond_init(&p_cond_,nullptr);pthread_cond_init(&c_cond_,nullptr);max_water=(maxcap*2)/3;min_water=maxcap/3;}
T pop()
{ pthread_mutex_lock(&mutex_);while(q_.size()==0){pthread_cond_wait(&c_cond_,&mutex_);}T out=q_.front();q_.pop();if(q_.size()<min_water)pthread_cond_signal(&p_cond_);pthread_mutex_unlock(&mutex_);return out;
}
void push(const T&in)
{
pthread_mutex_lock(&mutex_);
while(q_.size()==maxcap_)
{pthread_cond_wait(&p_cond_,&mutex_);}
q_.push(in);
if(q_.size()>max_water)
pthread_cond_signal(&c_cond_);
pthread_mutex_unlock(&mutex_);
}
~Blockqueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&p_cond_);pthread_cond_destroy(&c_cond_);}
private:
queue<T>q_;
int maxcap_;
pthread_mutex_t mutex_;
pthread_cond_t p_cond_;
pthread_cond_t c_cond_;
int max_water;
int min_water;
};