一、线程同步
同步:保证数据安全的条件下,让多执行流访问资源具有一定的顺序性,从而高效地使用临界资源
1.条件变量
(1)定义:当一个线程互斥访问某个变量时,它可能发现在其它线程改变状态之前,什么也做不了
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
pthread_cond_t :条件变量用来描述某种临界资源是否就绪的一种数据化描述
2. 条件变量相关函数
(1)初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL
(2)销毁
int pthread_cond_destroy(pthread_cond_t *cond)
(3)等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
(4)唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
3.例子:一个主线程控制其它线程
#include<iostream>
#include<pthread.h>
#include<stdio.h>
pthread_mutex_t lock;
pthread_cond_t cond;
//条件变量下阻塞等待,3个线程会逐个等待,再排队,循环
void *Run(void *arg)
{
pthread_detach(pthread_self()); //将当前线程先分离
std::cout << (char*)arg << "run..." << std::endl;
while(true)
{
pthread_cond_wait(&cond,&lock);//阻塞在这
std::cout << "thread: " << pthread_self() << "活动..." << std::endl;
}
}
int main()
{
//初始化
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
//设置3个线程,其中主线程可以控制其余3个线程
pthread_t t1,t2,t3;
//创建线程
pthread_create(&t1,nullptr,Run,(void*)"thread 1");
pthread_create(&t2,nullptr,Run,(void*)"thread 2");
pthread_create(&t3,nullptr,Run,(void*)"thread 3");
//主线程
while(true)
{
getchar();
// pthread_cond_signal(&cond); //唤醒线程
//唤醒全部线程
pthread_cond_broadcast(&cond);
}
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}
二、生产者消费者模型
1.模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
2.三关系、两角色、一场所(321)
三种关系:
(1)消费者与消费者:竞争关系,即互斥关系(谁申请到锁,资源就给谁)
(2)生产者与生产者:竞争关系,即互斥关系
(3)生产者与消费者:竞争关系(保证数据的正确),同步关系(多线程协同)
两种角色:
生产者、消费者:特指的时特定的线程或者进程
一个交易场所:
通常是内存中的缓冲区(自己通过某种方式组织起来)
3.为什么要有生产者消费者模型(优点)
(1)本质是对代码进行解耦的过程
(2)支持并发
(3)支持忙闲不均
4.基于BlockingQueue的生产者消费者模型
单生产单消费例子:生产者生产了数据,消费者就用数据
BlockQueue.hpp
#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
#define NUM 32
template<typename T>
class BlockQueue
{
private:
IsFull()
{
return q.size() == cap;
}
IsEmpty()
{
return q.empty();
}
public:
RingQueue(int _cap = NUM):cap(_cap),c_pos(0),p_pos(0)
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&full,nullptr);
pthread_cond_init(&empty,nullptr);
}
//本质是生产,生产数据,关心盒子资源
void Push(const T& in)
{
pthread_mutex_lock(&lock);
while(IsFull()) //需要使用while,条件不满足才会向后执行,若用if,等待调用函数失败,数据进入会造成出错
{
//wait q可以容纳新的数据
pthread_cond_wait(&full,&lock); //在特定条件变量下等待,和进入临界区等待时自动释放互斥锁,若等待被唤醒,又会自动获取互斥锁
}
q.push(in);
if(q.size() >= cap/2) //通知消费者来消费
{
std::cout << "数据已经很多了,快来消费" << std::endl;
pthread_cond_signal(&empty); //唤醒消费者
}
pthread_mutex_unlock(&lock);
//pthread_cond_signal(&empty);
}
//本质是消费,消费数据,关心data资源
void Pop(T& out)
{
pthread_mutex_lock(&lock);
while(IsEmpty())
{
//不能进行消费,需要等待,等待q有新的数据
pthread_cond_wait(&empty,&lock);
}
out = q.front();
q.pop();
if(q.size() <= cap/2)
{
std::cout << "数据不够了,快来生产" << std::endl;
pthread_cond_signal(&full); //唤醒生产者来生产
}
pthread_mutex_unlock(&lock);
//pthread_cond_signal(&full); //唤醒生产者
}
~RingQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
private:
std::queue<T> q;//临界资源
int cap;
pthread_mutex_t lock;
//条件变量,一个描述为满,一个描述为空
pthread_cond_t full;
pthread_cond_t empty;
};
main.cc文件
#include "BlockQueue.hpp"
#include<stdlib.h>
#include<unistd.h>
void *consume(void *arg)
{
//RingQueue<int> *rq = (RingQueue<int>*)arg;
auto bq = (BlockQueue<int>*) arg; //交易场所
while(true)
{
sleep(1); //等待生产者先生产,再消费
int data = 0 ;
bq->Pop(data);
std::cout << "consume done" << data << std::endl;
}
}
void *product(void *arg)
{
//RingQueue<int> *rq = (RingQueue<int>*)arg;
auto bq = (BlockQueue<int>*) arg;
while(true)
{
int data = rand()%100+1;
bq->Push(data);
std::cout << "product done" << data << std::endl;
}
}
int main()
{
srand((unsigned long)time(nullptr));
BlockQueue<int> *bq = new BlockQueue<int>();
pthread_t c,p;
pthread_create(&c,nullptr,consume,bq);
pthread_create(&p,nullptr,product,bq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
return 0;
}
注意:
1.pthread_cond_wait():在特定的条件变量下等待,等待往往在临界区等待,等待时,mute_x会自动释放互斥锁,如果当前等待被唤醒,又会获得自动对应的mute_x
2.pthread_cond_wait():是一个函数,让当前执行流进行等待的函数,有可能被调用失败,或者被伪唤醒
3.进行条件变量在资源临界区进行资源唤醒时,采用的循环用while,如果条件不满足,则会一直进行循环(不用if)
5.例子:计算式子,生产者生产任务,消费者进行计算
任务封装
#pragma once
#include<iostream>
class Task
{
private:
int x;
int y;
char op;
public:
Task(int _x, int_y, char _op):x(_x), y(_y), op(_op)
{}
void Run()
{
int result = 0;
switch(op)
{
case '+':
result = x + y;
break;
case '-':
result = x - y;
break;
case '*':
result = x * y;
break;
case '/':
if(y == 0)
{
std::cout << "Warning:div zero!" << std::endl;
result = -1;
}
else
{
result = x / y;
}
}
std:: cout << x << op << y << = << result << std::endl;
}
~Task()
{}
}
#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
#define NUM 32
template<typename T>
class RingQueue
{
private:
IsFull()
{
return q.size() == cap;
}
IsEmpty()
{
return q.empty();
}
public:
RingQueue(int _cap = NUM):cap(_cap)
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&full,nullptr);
pthread_cond_init(&empty,nullptr);
}
//本质是生产,生产数据,关心盒子资源
void Push(const T& in)
{
pthread_mutex_lock(&lock);
while(IsFull()) //需要使用while,条件不满足才会向后执行,若用if,等待调用函数失败,数据进入会造成出错
{
//wait q可以容纳新的数据
pthread_cond_wait(&full,&lock); //在特定条件变量下等待,和进入临界区等待时自动释放互斥锁,若等待被唤醒,又会自动获取互斥锁
}
q.push(in);
if(q.size() >= cap/2) //通知消费者来消费
{
std::cout << "数据已经很多了,快来消费" << std::endl;
pthread_cond_signal(&empty); //唤醒消费者
}
pthread_mutex_unlock(&lock);
//pthread_cond_signal(&empty);
}
//本质是消费,消费数据,关心data资源
void Pop(T& out)
{
pthread_mutex_lock(&lock);
while(IsEmpty())
{
//不能进行消费,需要等待,等待q有新的数据
pthread_cond_wait(&empty,&lock);
}
out = q.front();
q.pop();
if(q.size() <= cap/2)
{
std::cout << "数据不够了,快来生产" << std::endl;
pthread_cond_signal(&full); //唤醒生产者来生产
}
pthread_mutex_unlock(&lock);
//pthread_cond_signal(&full); //唤醒生产者
}
~RingQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
private:
std::queue<T> q;//临界资源
int cap;
pthread_mutex_t lock;
//条件变量,一个描述为满,一个描述为空
pthread_cond_t full;
pthread_cond_t empty;
};
main.cc文件
#include "Ring.hpp"
#include<stdlib.h>
#include<unistd.h>
#include"Task.hpp"
void *consume(void *arg)
{
RingQueue<int> *rq = (RingQueue<int>*)arg;
//auto bq = (BlockQueue<Task>*) arg;
while(true)
{
sleep(1); //等待生产者先生产,再消费
Task t;
bq->Pop(t);
t.Run();
std::cout << "consume Task done" << std::endl;
}
}
void *product(void *arg)
{
RingQueue<int> *rq = (RingQueue<int>*)arg;
//auto bq = (BlockQueue<Task>*) arg;
const char *arr="+-*/";
while(true)
{
int x = rand()%100+1;
int y = rand()%50;
char op = arr[rand()%4];
Task t(x,y,op);
bq->Push(t);
std::cout << "product Task done" << std::endl;
}
}
int main()
{
srand((unsigned long)time(nullptr));
RingQueue<Task> *bq = new RingQueue<Task>();
pthread_t c,p;
pthread_create(&c,nullptr,consume,bq);
pthread_create(&p,nullptr,product,bq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
return 0;
}
三、POSIX信号量
1.信号量
(1)信号量:本质是一个计数器,描述临界资源中资源数目的计数器
(2)信号量存在价值:同步、互斥、更细粒度的临界资源的管理
(3)申请到信号量的本质,并不是已经开始使用临界资源中所申请的那个区域,而是有了使用特定资源的权限
(4)申请信号量的本质:让计数器- -
释放信号量的本质:让计数器+
(5)信号量本质上也是临界资源,信号量的PV操作必须是原子的(P操作:申请信号量,- -;V操作:释放信号量,++)
定义一个全局变量count,++/- -是原子吗?
不是,count++/- -需要CPU读取数据,进行++、–,再释放数据
(6)进行PV操作的时候,一定有一种情况,P操作有可能申请不到,0,阻塞
2.POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。
sem的值为1基本等价于互斥锁
二元信号量:只有0 1
(1)初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
(2)销毁信号量
int sem_destroy(sem_t *sem);
(3)等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
(4)发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
例子:利用二元信号量(相当于互斥锁)来完后互斥功能(抢票)
#include<iostream>
#include<semaphore.h>
#include<pthread.h>
#include<string>
#include<unistd.h>
class Sem
{
private:
sem_t sem;
public:
Sem(int num)
{
sem_init(&sem,0,num);
}
void P()
{
sem_wait(&sem);
}
void V()
{
sem_post(&sem);
}
~Sem()
{
sem_destroy(&sem);
}
};
Sem sem(1);
int tickets = 1000;
void *GetTickets(void *arg)
{
std::string name = (char*)arg;
while(true)
{
sem.P();
if(tickets > 0)
{
usleep(10000);
std::cout << name << "get ticket:" << tickets-- << std::endl;
sem.V();
}
else
{
sem.V();
break;
}
}
std::cout << name << " quit" << std::endl;
pthread_exit((void*)0);
}
int main()
{
pthread_t tid1,tid2,tid3;
pthread_create(&tid1,nullptr,GetTickets,(void*)"thread 1"); //抢票时传入的参数是thread 1
pthread_create(&tid2,nullptr,GetTickets,(void*)"thread 2");
pthread_create(&tid3,nullptr,GetTickets,(void*)"thread 3");
//等待线程或者获取线程退出的状态,在这里仅等待线程
pthread_join(tid1,nullptr);
pthread_join(tid2,nullptr);
pthread_join(tid3,nullptr);
return 0;
}
3.基于环形队列的生产消费模型
环形队列采用数组模拟,用模运算来模拟环状特性
生产者:关心空间
消费者:关心数据
两执行流遵守的原则:
(1)生产和消费不能指向同一位置
(2)无论是生产者还是消费者,都不应该将对方“套圈”
Ring.hpp文件
#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
#define NUM 32
template<typename T>
class RingQueue
{
private:
std::vector<T> q;
int cap;
int c_pos; //消费者位置
int p_pos; //生产者位置
sem_t blank_sem; //空间资源
sem_t data_sem; //数据资源
private:
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t &s)
{
sem_post(&s);
}
public:
RingQueue(int _cap = NUM):cap(_cap),c_pos(0),p_pos(0)
{
q.resize(cap);
sem_init(&blank_sem,0,cap); //开始盒子的空间相当于全空,都可以存放
sem_init(&data_sem,0,0);//数据资源没有
}
//本质是生产,生产数据,关心盒子资源
void Push(const T& in)
{
P(blank_sem);//申请格子资源
q[p_pos] = in;
V(data_sem);
p_pos++;
p_pos %= cap;
}
//本质是消费,消费数据,关心data资源
void Pop(T& out)
{
P(data_sem);
out = q[c_pos];
V(blank_sem); //数据被拿走了,格子空出来
c_pos++;
c_pos %= cap;
}
~RingQueue()
{
sem_destroy(&blank_sem);
sem_destroy(&data_sem);
}
};
main.cc文件
#include "Ring.hpp"
#include<stdlib.h>
#include<unistd.h>
void *consume(void *arg)
{
RingQueue<int> *rq = (RingQueue<int>*)arg;
while(true)
{
sleep(1);
int x= 0 ;
rq->Pop(x);
std::cout << "consume done" << x << std::endl;
}
}
void *product(void *arg)
{
RingQueue<int> *rq = (RingQueue<int>*)arg;
while(true)
{
int x = rand()%100+1;
rq->Push(x);
std::cout << "product done" << x << std::endl;
}
}
int main()
{
srand((unsigned long)time(nullptr));
RingQueue<int> *rq = new RingQueue<int>();
pthread_t c,p;
pthread_create(&c,nullptr,consume,rq);
pthread_create(&p,nullptr,product,rq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
return 0;
}
注意:
(1)是否有数据不一致的问题?
没有:只有两种情况指向同一位置,只有指向了同一位置,才有临界资源竞争问题(指向同一位置:为空或为满)