Linux多线程学习总结
一.生产者-消费者模型
1. 什么是生产者-消费者模型
在线程的同步和互斥
中,有一个生产者-消费者模型
的经典问题,它也称为有界缓冲区
。两个进程共享一个公共的固定大小
的缓冲区。其中一个是生产者
,将信息放入缓冲区
,另一个是消费者
,从缓冲区中取信息
。既一个或多个生产者(线程或进程)
创建着一些数据
放入缓冲区,然后这些数据由一个或多个消费者(线程或进程)
处理,从缓冲区拿走。数据在生产者和消费者
之间可以通过某种IPC
传递。
2.生产者-消费者模型的三种关系
生产者-生产者
:互斥生产者-消费者
:同步和互斥消费者-消费者
:互斥
3.基于BlockQueue实现生产者-消费者模型
生产者-消费者模型
问题的关键在于缓冲区已满
,而此时生产者
还想往其中放入一个新的数据
的情况。其解决办法是让生产者睡眠
,待消费者从缓冲区
中取出一个或多个数据
时再唤醒
它。同样的, 当消费者
试图从缓冲区中取数据
而发现缓冲区空
时,消费者就睡眠
,直到生产者
向其中放一些数据
后再将其唤醒
。
//cp.hpp
#ifndef __CP_HPP__
#define __CP_HPP__
#include <iostream>
#include <queue>
#include <mutex>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <pthread.h>
using namespace std;
template<class T>
class BlockQueue{
private:
//给队列上锁
void LockQueue()
{
pthread_mutex_lock(&lock);
}
//解锁队列
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
//判断队列是否满
bool IsFull()
{
return q.size() == (unsigned int)cap ? true : false;
}
//判断杜烈是否为空
bool IsEmpty()
{
return q.size() == 0 ? true : false;
}
//生产者在条件变量下阻塞等待
void ProductWait()
{
pthread_cond_wait(&p_cond, &lock);
}
//消费者在条件变量下阻塞等待
void ConsumeWait()
{
pthread_cond_wait(&c_cond, &lock);
}
//使生产者的条件得到满足,唤醒该线程
void SignalProduct()
{
pthread_cond_signal(&p_cond);
}
//使消费者的条件得到满足,唤醒该线程
void SignalConsume()
{
pthread_cond_signal(&c_cond);
}
//判断是否达到高水位线(达到2/3队列的长度既通知消费者来消费)
bool IsHighLine()
{
return q.size() > (unsigned int)high_line ? true : false;
}
//判断是否达到低水位线(低于到1/3队列的长度既通知生产者来消费)
bool IsLowLine()
{
return q.size() < (unsigned int)low_line ? true : false;
}
public:
//构造函数
BlockQueue(const int& _cap)
:cap(_cap)
,high_line((_cap*2)/3)
,low_line((_cap*1)/3)
{
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&p_cond, NULL);
pthread_cond_init(&c_cond, NULL);
}
//生产者向缓冲区放数据(同步与互斥)
void PushData(int& data)
{
LockQueue();//互斥锁实现互斥
while(IsFull())//使用while进行二次判断,防止假唤醒
{
ProductWait();
}
//高于水位线则通知消费者来消费
if(IsHighLine())
{
SignalConsume();//条件变量实现同步
}
q.push(data);
UnlockQueue();
}
void PopData(int& data)//输出型参数,通过参数带回返回值
{
LockQueue();
//使用while进行二次判断,防止假唤醒
while(IsEmpty())
{
ConsumeWait();
}
//低于水位线则通知生产者来生成
if(IsLowLine())
{
SignalProduct();
}
data = q.front();
q.pop();
UnlockQueue();
}
//析构函数,销毁互斥量和条件变量
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&p_cond);
pthread_cond_destroy(&c_cond);
}
private:
queue<T> q;
int cap;//阻塞队列的最大长度
int high_line;//高水位线
int low_line;//低水位线
pthread_mutex_t lock;
pthread_cond_t p_cond;//队空,生产者生产
pthread_cond_t c_cond;//队满,消费者消费
};
#endif //__CP_HPP__
//cp.cc
#include "cp.hpp"
const int num = 10;//阻塞队列的最大长度
void *consume_routine(void *arg)
{
BlockQueue<int>* pbq = (BlockQueue<int>*)arg;
int data;
for(;;)
{
pbq -> PopData(data);
cout << "consume done, data is " << data << endl;
sleep(1);
}
}
void *product_routine(void *arg)
{
BlockQueue<int>* pbq = (BlockQueue<int>*)arg;
srand((unsigned long)time(NULL));
for(;;)
{
int data = rand() % 100 + 1;
pbq -> PushData(data);
cout << "product done,data is " << data << endl;
sleep(1);
}
}
int main()
{
BlockQueue<int> *pbq = new BlockQueue<int>(num);
pthread_t p,c;//product(生产者)、consume(消费者)
pthread_create(&c, NULL, consume_routine, (void*)pbq);
pthread_create(&p, NULL, product_routine, (void*)pbq);
//等待线程终止
pthread_join(p, NULL);
pthread_join(c, NULL);
//释放开辟在栈上的队列
delete pbq;
return 0;
}
上边的阻塞队列
实现的生产者-消费者
模型只实现了生产者-消费者
之间的同步与互斥
关系,如果要实现生产者和生产者、消费者和消费者
之间的互斥
关系,还应该增加一些生产者和消费者
,并且在生产和消费的时候加上互斥锁
,即可实现他们之间的互斥
关系。
注:上述问题的实现代码位于:https://github.com/hansionz/Linux_Code/tree/master/pthread/cp
二.POSIX信号量
1.什么是POSIX信号量
在学习进程间通信
的时候,接触学习过SystemV版本
的信号量,一个二元信号量就相当于一把互斥锁。而POSIX
信号量和SystemV
信号量作用是相同的,都是用来实现进程和线程间同步操作的,从而可以达到无冲突的共享资源
的目的。它们两个的区别在于SystemV版本
的信号量在内核中维护,而POSIX信号量
存放在共享内存区。
2.初始化信号量
信号量是一个sem_t
类型的变量,要使用POSIX信号量必须引入头文件#include <semaphore.h>
,在使用之前一定要初始化。
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表⽰线程间共享,非0表示进程间共享
value:信号量初始值
3. 销毁信号量
int sem_destroy(sem_t *sem);
参数:
sem:代表要销毁信号的地址
4. 等待信号量
等待信号量相对SystemV版本
信号量的P操作
。
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
5.发布信号量
发布信号量相对于SystemV版本
信号量的V操作
。
功能:发布信号量,表示资源使⽤用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);
6.基于POSIX信号量实现的环形队列模拟的生产者-消费者模型
//cp.hpp
#ifndef __CP_HPP__
#define __CP_HPP__
#include <iostream>
#include <queue>
#include <mutex>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
using namespace std;
template<class T>
class RingQueue{
private:
//等待信号量,信号量减1
void P(sem_t& sem)
{
sem_wait(&sem);
}
//发布信号量,信号量加1
void V(sem_t& sem)
{
sem_post(&sem);
}
public:
//构造函数,在构造中必须先将vector初始化一定长度代表空格信号量的个数
RingQueue(int cap)
:_cap(cap)
,ring(cap)
{
c_step = p_step = 0;
sem_init(&blank_sem, 0, _cap);
//数据信号量初始为0
sem_init(&data_sem, 0, 0);
}
//向队列中入一个数据,要保证同步与互斥
void PushData(const T& data)
{
P(blank_sem);
ring[p_step] = data;
p_step++;
p_step %= _cap;
V(data_sem);
}
//从循环队列出一个数据
void PopData(int& data)
{
P(data_sem);
data = ring[c_step];
c_step++;
c_step %= _cap;
V(blank_sem);
}
~RingQueue()
{
sem_destroy(&blank_sem);
sem_destroy(&data_sem);
}
private:
vector<T> ring; //vector模拟实现环形队列
int _cap; //队列的长度
sem_t blank_sem;
sem_t data_sem;
int c_step;//消费者步伐
int p_step;//生产者步伐
};
#endif //__CP_HPP__
//cp.cc
#include "cp.hpp"
const int num = 10; //空格信号量个数
void *consume_routine(void *prq)
{
RingQueue<int>* q = (RingQueue<int>*)prq;
int data;
for(;;)
{
q -> PopData(data);
cout << "consume done,data is:" << data << endl;
}
}
void *product_routine(void *prq)
{
RingQueue<int>* q = (RingQueue<int>*)prq;
srand((unsigned long)time(NULL));
for(;;)
{
int data = rand() % 100 + 1;
q -> PushData(data);
cout << "product done,data is:" << data << endl;
sleep(1);
}
}
int main()
{
RingQueue<int>* prq = new RingQueue<int>(num);
pthread_t p,c;
pthread_create(&p, NULL, product_routine, (void*)prq);
pthread_create(&c, NULL, consume_routine, (void*)prq);
pthread_join(p, NULL);
pthread_join(c, NULL);
delete prq;
prq = nullptr;
return 0;
}
注:代码于:https://github.com/hansionz/Linux_Code/tree/master/pthread/ring_cp