常见锁的概念
死锁
死锁是指在一组进程中各个进程均占有不会释放的资源,但因互相申请被其他进程所占用的不会释放的资源而处于一种永久等待的状态。简单来说就是两个线程都
在等待对方释放锁。
死锁必要条件
必须同时满足四个条件才会产生死锁
1.互斥条件:一个资源每次只能被一个执行流使用
2.请求与保持条件(持有并等待条件):一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
3.不可剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺。
4.环路等待条件:若干执行流之间形成一种头尾详解的循环等待条件。
解决死锁问题(资源有序分配)
破坏4个必要条件之一即可,最常见的并且可行的就是使用资源有序分配法,来破环环路等待条件。
那什么是资源有序分配法呢?
线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。
其他方法
死锁检测算法、银行家算法。
线程同步
同步问题是保证数据安全的情况下,让线程访问资源具有一定的顺序性从而避免饥饿问题。如果没有适当的同步机制,就可能出现数据不一致、数据竞态条件(race condition)或其他不可预测的行为。
条件变量
允许线程等待某个条件成立(通常与互斥锁一起使用)。当条件不满足时,线程会阻塞并等待其他线程发出信号。它是在多线程程序中用来实现“等待–>唤醒”逻辑常用的方法。它用于维护一个条件,这个条件与条件变量是不同的概念。线程可以使用条件变量来等待某个条件为真,注意这里并不是等待条件变量为真。它允许线程在等待某个条件成立时进入休眠状态,从而避免了不必要的CPU资源浪费,提高了程序的效率和响应速度.
相关函数
初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
????为何要带锁
在让线程在条件变量时等待时,会自动释放传入的锁。当被唤醒该函数返回时会重新持有锁。
如何判断临界资源是否就绪?通过访问临界资源,也必须加锁。等待过程所以必须在加锁和解锁之间。
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
signal唤醒等待在该条件变量的一个线程,broadcast唤醒等待在该条件变量下的所有线程。
生产者消费者模型
生产者消费者模型(Producer-Consumer Model)是一种常用的并发编程模型,主要用于解决生产数据(或任务)和消费数据(或任务)之间的平衡问题。
生产者(Producer):主要负责生成数据(或任务),并将其放入一个共享的数据缓冲区(如队列、栈等)。当缓冲区满时,生产者通常会等待,直到缓冲区中有空间可以存放新的数据。
消费者(Consumer):主要负责从共享的数据缓冲区中取出数据(或任务)并进行处理。当缓冲区为空时,消费者通常会等待,直到缓冲区中有新的数据可供消费。
基于阻塞队列的CP模型
block.hpp
#pragma once
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>
#include<mutex>
template<class T>
class Blockq{
static const int default_num=20;
public:
Blockq(int maxcap=default_num)
:maxcap_(maxcap)
{
pthread_mutex_init(&mutex_,nullptr);
pthread_cond_init(&p_cond_,nullptr);
pthread_cond_init(&c_cond_,nullptr);
low_water_=maxcap_/3;
high_water_=(maxcap_*2)/3;
}
T pop()
{
pthread_mutex_lock(&mutex_);
while(q_.size()==0)//防止伪唤醒,用while判断
{
pthread_cond_wait(&c_cond_,&mutex_);
}
T out=q_.front();
q_.pop();
if(q_.size()<low_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()>high_water_)
pthread_cond_signal(&c_cond_);
pthread_mutex_unlock(&mutex_);
}
~Blockq()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&c_cond_);
pthread_cond_destroy(&p_cond_);
}
private:
std::queue<T> q_;
int maxcap_;
pthread_mutex_t mutex_;
pthread_cond_t p_cond_;
pthread_cond_t c_cond_;
int low_water_;
int high_water_;
};
main.cc
#include"BlockQueue.hpp"
#include"Task.hpp"
#include<ctime>
#include<unordered_map>
std::string ops="+-*/%";
void * Consumer(void* args)
{
Blockq<Task>* bq=static_cast<Blockq<Task>*>(args);
while(true)
{
//消费
Task t=bq->pop();
std::cout<<pthread_self()<<" :"<<"消费一个任务:";
t.run();
}
}
void * Productor(void* args)
{
Blockq<Task>* bq=static_cast<Blockq<Task>*>(args);
while(true)
{
int data1=rand()%10+1;
int data2=rand()%10+1;
char op=ops[rand()%5];
Task t(data1,data2,op);
usleep(10);
bq->push(t);
std::cout<<"生产了一个任务:"<<std::endl;
}
}
int main()
{
srand(time(nullptr));
Blockq<Task>* bq=new Blockq<Task>();
pthread_t c[3],p[5];
for(int i=0;i<3;i++)
{
pthread_create(c+i,nullptr,Consumer,bq);
}
for(int i=0;i<5;i++)
{
pthread_create(p+i,nullptr,Productor,bq);
}
for(int i=0;i<3;i++)
{
pthread_join(c[i],nullptr);
}
for(int i=0;i<5;i++)
{
pthread_join(p[i],nullptr);
}
delete bq;
return 0;
}
task.hpp
#pragma once
#include<iostream>
class Task{
private:
int _a;
int _b;
char _op;
int _result;
public:
Task(int a,int b,char op)
:_a(a)
,_b(b)
,_op(op)
,_result(0){}
void run()
{
switch (_op)
{
case '+':
_result=_a+_b;
break;
case '-':
_result=_a-_b;
break;
case '*':
_result=_a*_b;
break;
case '/':
_result=_a/_b;
break;
case '%':
_result=_a%_b;
break;
default:
break;
}
std::cout<<_a<<_op<<_b<<"="<<_result<<std::endl;
}
};
makefile
blockqueue:main.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -rf blockqueue
基于信号量和环形队列的CP模型
见上篇博客
读者写者模型
读者写者模型是操作系统中的一种同步与互斥机制,主要用于处理对共享资源的并发访问问题。在这个模型中,存在两种角色:读者和写者,他们共享一个读写场所。
读者是对共享资源进行读取操作的一方,多个读者可以同时并发地读取资源,这是因为读操作并不改变资源的内容,因此读者之间是共享关系。而写者则是进行写操作的一方,写操作会修改共享资源的内容,因此具有排他性,即同一时刻只能有一个写者进行写操作,写者之间是互斥关系。