众所周知,线程是共享地址空间的,线程会共享大部分资源,所以会造成数据不一致问题,这个多线程执行力流被保护的共享资源的代码,就叫作临界资源,而每个线程内部,访问临界资源的代码,就叫做临界区
如下图:
#include
#include
#include
#include
#include
int ticket = 100;
void *route(void *arg)
{
char *id = (char*)arg;
while ( 1 ) {
if ( ticket > 0 ) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
} else {
break;
}
}
}
int main( void )
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, (void*)"thread 1");
pthread_create(&t2, NULL, route, (void*)"thread 2");
pthread_create(&t3, NULL, route, (void*)"thread 3");
pthread_create(&t4, NULL, route, (void*)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
一次执⾏结果:
thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1
thread 3 sells ticket:-2
在上面代码中票已经变成负数了
所以互斥是任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
所以我们要如何去解决上述问题呢?我们就引入锁的概念
锁

锁也是pthread库提供
定义锁
1.静态分配:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER
2动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
注意:动态分配需要去释放锁:int pthread_mutex_destroy(pthread_mutex_t *mutex);
使用锁
加锁:竞争申请锁,多线程锁被本来就是临界资源,所以申请锁的过程必须是原子的
成功:继续向后运行,访问临界区代码,访问临界资源
失败:阻塞挂起申请执行流
加锁与解锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
int pthread_mutex_trylock(pthread_mutex_t *mutex);申请非阻塞版本
锁提供的本质:执行临界区代码由并行转化为串行
锁的原理:
硬件实现:关闭时钟中断
软件实现:为了实现互斥锁操作,大多数体系结构都是供了swap或exchange指令,
该指令的作用是把寄存器和内存单元的数据相交换

我们用swap,exchange将内存中的变量,交换到cpu的寄存器中:
本质是检测al里面的内容看是否>0,不满足条件就挂起,然后让满足条件的去切换回来
面向对象去封装使用:
#pragma once
#include
#include
namespace MutexMudole{
class Mutex{
private:
pthread_mutex_t mutex;
public:
Mutex(){
pthread_mutex_init(&mutex,NULL);
}
~Mutex(){
pthread_mutex_destroy(&mutex);
}
void Lock(){
pthread_mutex_lock(&mutex);
}
void UnLock(){
pthread_mutex_unlock(&mutex);
}
};
}
此时我们就可以直接使用了
但是此时我们能不能让他自己释放呢,而不是手动去释放锁,此时我们就只需要再加一个类就可以完成下面需求
class LockGuard{
public:
LockGuard(Mutex& mutex):mutex_(mutex){
mutex_.Lock();
}
~LockGuard(){
mutex_.UnLock();
}
private:
Mutex& mutex_;
};
所以原来的代码就变成了这样(下面),就可以实现加锁操作
#include
#include
#include
#include
#include
#include
#include
#include"Mutex.hpp"
using namespace MutexMudole;
//pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
int ticket = 100;
class ThreadData{
public:
std::string name;
Mutex *lockp;
ThreadData(const std::string& name,Mutex &lockp):name(name),lockp(&lockp) {}
};
void *route(void *arg)
{
ThreadData *td=(ThreadData*)arg;
while ( 1 ) {
LockGuard guard(*td->lock);//加锁完成。RAII风格的实现互斥锁
if ( ticket > 0 ) {
usleep(1000);
printf("%s sells ticket:%d\n", td->name.c_str(), ticket);
ticket--;
} else {
break;
}
}
}
int main( void )
{
Mutex lock;
pthread_t t1, t2, t3, t4;
ThreadData *td1=new ThreadData("thread 1",lock);
ThreadData *td2=new ThreadData("thread 2",lock);
ThreadData *td3=new ThreadData("thread 3",lock);
ThreadData *td4=new ThreadData("thread 4",lock);
pthread_create(&t1, NULL, route, (void*)td1);
pthread_create(&t2, NULL, route, (void*)td2);
pthread_create(&t3, NULL, route, (void*)td3);
pthread_create(&t4, NULL, route, (void*)td4);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
return 0;
}
LockGuard guard(*td->lock),此时就只需要去使用这个临时变量就可以完成加锁操作
上面也就是c++里面lock_guard和mutex的底层原理
二.线程同步
同步:
当一个线程高频的申请锁,会造成线程饥饿问题,所以让线程安全的前提下让所有执行流,访问临界资源,按照一定的顺序进行访问资源就叫作:同步
条件变量:
当一个线程互斥地访问其个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就
需要用到条件变量。
条件变量接口:

他与锁有些相似,但条件变量是用来保证线程同步的,锁是保证线程互斥的

第一个是唤醒指定变量中的所有线程,第二个接口则是唤醒其中一个线程
下面是一个小demo:
#include
#include
#include
#include
#include
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
#define NUM 5
int cnt=1000;
void* thread_func(void*arg){
std::string name=(char*)arg;
while(true){
pthread_mutex_lock(&lock);
//直接让线程进行等待
pthread_cond_wait(&cond,&lock);
//线程被唤醒后,进行操作
std::cout<<" wake up"< threads;
for(int i=0;i<5;i++){
pthread_t tid;
std::string name="thread"+std::to_string(i);
int ret=pthread_create(&tid,NULL,thread_func,(void*)name.c_str());
if(ret!=0){
continue;
}
threads.push_back(tid);
sleep(1);
}
while(true){
//pthread_cond_signal(&cond);
pthread_cond_broadcast(&cond);
sleep(1);
}
for(auto&tid:threads){
int m=pthread_join(tid,NULL);
}
return 0;
}
三.生产者消费者模型:
本质:多线程通信
理解:
生产者和生产者:竞争关系,互斥关系
消费者和消费者: 互斥关系
生产者和消费者之间:互斥和同步
2中角色:生产者角色和消费者角色(线程承担)
1个交易场所:以特定结构构成的一直"内存"空间
生产者消费者模型的好处
1.生产过程和消费过程解耦
2.支持忙闲不均
3.提高效率
例子:管道间通信,一个写一个读
代码示例:
基于blockqueue的生产者消费者模型
阻塞队列图:
在多线程编程中阻蒸队列(blockingqueue)是一种常用于实现生产者和消费者模型的数据结构。
其与普通的队列区别在于,当队列为空时,从队列获取元索的操作将会被阻塞,直到队列中被放
入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(上
的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

阻塞队列是一个容量具有上限的队列。不满足读写条件时,会被阻塞
#pragma once
#include
#include
#include
#include
const int default_capacity=5;
template
class BlockQueue{
public:
BlockQueue(int cap=default_capacity):_cap(cap),csleep_num(0),psleep_num(0){
pthread_mutex_init(&_mutex,NULL);
pthread_cond_init(&_full_cond,NULL);
pthread_cond_init(&_empty_cond,NULL);
}
void Equeue(const T&val){
//生产者调用
pthread_mutex_lock(&_mutex);
while(IsFull()){
//阻塞等待
//当被唤醒时必须还要竞争_mutex这把锁才能向下运行
psleep_num++;
//伪唤醒:条件不满足也被唤醒了
pthread_cond_wait(&_full_cond,&_mutex);
psleep_num--;
}
//使用while再检查一次100%确定不是伪唤醒
//入队
q.push(val);
//通知消费者
if(csleep_num>0){
pthread_cond_signal(&_empty_cond);
}
pthread_mutex_unlock(&_mutex);
}
T Pop(){
//消费者调用
pthread_mutex_lock(&_mutex);
while(IsEmpty()){
//阻塞等待
csleep_num++;
pthread_cond_wait(&_empty_cond,&_mutex);
csleep_num--;
}
//出队
T val=q.front();
q.pop();
//通知生产者
if(psleep_num>0){
pthread_cond_signal(&_full_cond);
}
pthread_mutex_unlock(&_mutex);
return val;
}
~BlockQueue(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_full_cond);
pthread_cond_destroy(&_empty_cond);
}
private:
bool IsFull(){
return q.size()==_cap;
}
bool IsEmpty(){
return q.empty();
}
std::queue q;
int _cap;//容量大小
pthread_mutex_t _mutex;
pthread_cond_t _full_cond;//生产者唤醒条件变量
pthread_cond_t _empty_cond;//消费者唤醒条件变量
int csleep_num;//消费者的个数
int psleep_num;//生产者的个数
};
生产者消费者提高生产效率体现在 于未来获取任务和处理具体任务,是并发
今天的更新就到这里,如有错误欢迎评论区指出
评论
我热爱, 这里分享真实经验。你的网站 就是 属于这里的。很出色。
所有文章都令人印象深刻。感谢 带来的灵感。 厄瓜多爾 确实少有, 这么鲜明的文字。谢谢。