1.本质上:PCB等待队列+计数器
2.计数器:对资源的计数,会影响信号量的等待接口和发送
3.接口(唤醒接口)的逻辑:
问题一: 当一个线程调用的发送接口之后,资源计数器进行加1操作,此时,加1操作之后的资源计数器的结果还是小于0,此时需要通知PCB等待队列吗?
需要,虽然资源计数器的结果还是小于0,但是信号量中的可用资源会有一个,这时候就需要去通知PCB等待队列当中的数据去进行处理。
问题二: 当一个线程调用发送接口之后,资源计数器进行加1操作,此时,加1操作之后的资源计数器的结果还是大于0,此时需要通知PCB等待队列吗?
不需要,因为现在资源计数器都还没使用完,所以PCB等待队列中也不会有东西。
sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:信号量
pshared:用于进程间还是用于线程间
线程间:0
进程间:非0
value:初始化资源的数量
int sem_wait(sem_t *sem);
1.会对资源计数器进行减1操作
2.判断资源计数器的值是否大于0
是:接口返回
否:将线程放在PCB等待最烈,阻塞起来
int sem_post(sem_t *sem);
1.会对资源计数器进行加1操作
2.判断资源计数器的值是否小于0
是:通知PCB等待队列
否:不通知PCB等待队列
1.线程安全的队列(vector)
互斥+同步(信号量)
2.两种角色的线程
#include
#include
#include
#include
#include
using namespace std;
#define CAPACITY 10
#define THREAD_NUM 2
class RingQueue
{
public:
RingQueue():vec_(CAPACITY)
{
//设置队列容量
capacity_ = CAPACITY;
//信号量初始化 线程间 初始化资源的数量为0
sem_init(&sem_lock_, 0, 1);
sem_init(&sem_read_, 0, 0);
sem_init(&sem_write_, 0, capacity_);
//设置读写下标
pos_read_ = 0;
pos_write_ = 0;
}
~RingQueue()
{
//销毁信号量资源
sem_destroy(&sem_lock_);
sem_destroy(&sem_read_);
sem_destroy(&sem_write_);
}
void Push(int data)
{
//对资源计数器进行减1操作
//判断资源计数器的值是否大于0
//大于0 接口返回执行下面的代码
//小于0 将线程放在PCB等待队列阻塞起来
sem_wait(&sem_write_);
sem_wait(&sem_lock_);
//将数据的值写入
vec_[pos_write_] = data;
//写的下标加1
pos_write_ = (pos_write_ + 1) % capacity_;
printf("i write %d, i am %p\n", data, pthread_self());
//会对资源计数器进行加1操作
//判断资源计数器的值是否小于0
//小于0 通知PCB等待队列
//大于0 不通知PCB等待队列
sem_post(&sem_lock_);
sem_post(&sem_read_);
}
void Pop(int* data)
{
sem_wait(&sem_read_);
sem_wait(&sem_lock_);
*data = vec_[pos_read_];
pos_read_ = (pos_read_ + 1) % capacity_;
printf("i read %d, i am %p\n", *data, pthread_self());
sem_post(&sem_lock_);
sem_post(&sem_write_);
}
private:
vector vec_;
int capacity_;
//保证互斥的信号量
sem_t sem_lock_;
//保证同步的信号量
sem_t sem_write_;
sem_t sem_read_;
//标识读写的位置
int pos_read_;
int pos_write_;
};
void* ReadStart(void* arg)
{
RingQueue* rq = (RingQueue*)arg;
while(1)
{
int data;
rq->Pop(&data);
}
return NULL;
}
int g_data = 1;
sem_t g_sem_lock_;
void* WriteStart(void* arg)
{
RingQueue* rq = (RingQueue*)arg;
while(1)
{
sem_wait(&g_sem_lock_);
rq->Push(g_data++);
sem_post(&g_sem_lock_);
}
}
int main()
{
sem_init(&g_sem_lock_, 0, 1);
RingQueue* rq = new RingQueue();
if(rq == NULL)
{
return 0;
}
pthread_t cons[THREAD_NUM];
pthread_t prod[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
int ret = pthread_create(&cons[i], NULL, WriteStart, (void*)rq);
if(ret < 0)
{
perror("pthread_create fail");
return 0;
}
ret = pthread_create(&prod[i], NULL, ReadStart, (void*)rq);
if(ret < 0)
{
perror("pthread_create fail");
return 0;
}
}
for(int i = 0; i < THREAD_NUM; i++)
{
pthread_join(cons[i], NULL);
pthread_join(prod[i], NULL);
}
delete rq;
sem_destroy(&g_sem_lock_);
return 0;
}
条件变量: 需要程序员自己把握资源的数量
信号量: 信号量会自己维护资源的数量,只需要在初始化信号量的时候,指定资源的数量。信号量可以完成互斥,也可以完成同步。
互斥: 资源计数器的初始值设置为1,线程A拿到信号量,则线程B一定拿不到
同步: 原理:进程获取临界资源之前,要先获取信号量资源,若无信号量资源,则该进程阻塞等待,进入等待队列,若有信号量资源,则对信号量进行P(-1)操作,再获取临界资源。
本质: 线程池当中包含一个线程安全的队列+一大堆的线程
线程池当中的线程都是从线程池当中的线程安全队列当中获取元素进行处理。逻辑上属于消费线程,线程池当中的线程执行同样的入口函数,执行的是同样的代码。
一个服务端后台的代码执行的业务是非常之庞杂的,如果一个线程池,只能处理一个类型的数据,则需要对每一个业务数据都创建一个线程池,这个无疑是消耗巨大的一件事。
问题: 怎么让一个线程池,可以处理多种多样的问题?
设计线程安全队列:
1.线程安全(互斥+同步)
互斥锁+条件变量
信号量
2.设计线程安全队列的元素类型
元素类型:待要处理的数据+处理该数据的方法(函数指针保存函数地址)