目录
一、学习的知识点
有了互斥锁 为什么还要条件变量
1 互斥锁
互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
需要注意的是,条件变量需要配合互斥锁来使用:
为什么要与pthread_mutex 一起使用呢? 这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.
简而言之就是,在thread 1 call pthread_cond_wait() 的时刻到 thread 1真正进入 wait 状态时,是存在着时间差的。如果在这段时间差内 thread2 调用了 pthread_cond_signal() 那这个 signal 信号就丢失了。给 wait 加锁可以防止同时有另一个线程在 signal。
实际应用把服务器分为两个进程
1.前置服务器
- 接收客户端的消息 -》 写到共享内存
- 发送消息给后置服务器 -》通知后置服务器取数据
1.1方案1 来一个客户端开一个线程
如果一个任务处理一个线程,一个线程处理完一个任务就结束,那么频繁接入任务就要频繁创建线程,比较耗时间.
优点:简单粗暴、奢侈
缺点:奢侈
此方案适合 计算密集型任务
1.2方案2 线程池
直接开启多个线程,等待任务接入。
一个线程处理完一个任务,继续处理下一个任务。
优点:利用率高
缺点:任务的队列:缓冲
如果任务的时间长,此方案不合适,此方案适合 I/O密集型任务
- 任务类
class CTask
{
public:
CTask() {};
void run() { cout << num << "is run" << endl; };
int num;
CTask(int i) { num = i; };
private:
};
- 线程池结构体
typedef struct thread_pool
{
pthread_cond_t cond;
pthread_mutex_t mutex;
int idle;//当前空闲线程的数量, 如果空闲线程 》 0 我就可以唤醒 如果没有空闲线程 创建新线程
int count;//当前有多少前程
queue<CTask*> task;//栈 任务队列
}THREAD_POOL_T;
- 线程池结构体初始化函数 初始化线程池结构体 创建空闲线程
void thread_pool_init(THREAD_POOL_T* pool,int i)
{
pthread_cond_init(&pool->cond, NULL);//初始化条件变量
pthread_mutex_init(&pool->mutex, NULL);//初始化锁
pool->idle = 0; //空闲线程数
pool->count = i; //总线程个数
for (int i = 0; i < pool->count; ++i)
{
pthread_t tid;
pthread_create(&tid, NULL, start_routine, pool);
}
}
- 线程池添加任务函数
void thread_pool_add_task(THREAD_POOL_T* pool, CTask* t)
{
pthread_mutex_lock(&pool->mutex); //加锁 放置很多线程同时存任务
pool->task.push(t); //把任务添加到线程池的队列
//有? 先去判断pool里面有没有空闲的线程
//判断有没有空闲线程 如果有 就唤醒线程
if (pool->idle > 0)
{
pthread_cond_signal(&pool->cond); //通知睡眠的进程 如果线程在忙 则无法唤醒
}
pthread_mutex_unlock(&pool->mutex);
}
- 线程回调函数
void* start_routine(void* arg)
{
THREAD_POOL_T* pool = (THREAD_POOL_T*)arg;
while (1)
{
pthread_mutex_lock(&pool->mutex); //加锁 放置很多线程同时取任务
pool->idle++; //进入线程 还未做任务空闲线程+1
//创建完成之后,目标是去执行线程池任务
//有任务和没任务的情况
//判断队列是否为空
if (pool->task.empty()) //没任务
{
//没任务就阻塞在此 等待被唤醒
cout << "thread wait..." << pthread_self() << endl;
pthread_cond_wait(&pool->cond, &pool->mutex);//条件变量
//等待被唤醒 被唤醒后返回 同时上锁
cout << "wake up..." << pthread_self() << endl;
}
//有任务 取任务
pool->idle--; //空闲线程 -1
CTask* t = pool->task.front();
pool->task.pop();
pthread_mutex_unlock(&pool->mutex);
t->run();
}
return NULL;
}
- 主函数
int main()
{
THREAD_POOL_T pool;//线程池结构体
thread_pool_init(&pool,5); //线程池初始化初始化
while (getchar())
{
cout << "into" << endl;
for (int i = 0; i < 10; ++i)
{
CTask* t = new CTask();
thread_pool_add_task(&pool, t); //添加任务队列、唤醒或创建线程
}
}
return 0;
}
2.后置服务器
- 收到信号,从共享内存取数据进行处理
- 处理好的数据写入共享内存 通知前置服务器取数据
二、上课没有听懂或者没有理解的地方
无