来看个 购票的 代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
#include <string.h>
int ticket = 100;
void* sell(void * arg)
{
char** id = (char**) arg;
while(1)
{
if(ticket > 0)
{
usleep(234212);//模拟购票过程 //这样会导致 并发执行的出错 ,即线程切换,出现的读脏数据,解决策略是 加锁
printf("thread %s 购票成功,当前余量%d\n",*id,--ticket);
}
else
{
break;
}
}
return NULL;
}
int main ()
{
pthread_t t1,t2,t3,t4;
std::string a[4]={"thread 1","thread 2","thread 3","thread 4"};
pthread_create(&t1,NULL,sell,&a[0]);
pthread_create(&t2,NULL,sell,&a[1]);
pthread_create(&t3,NULL,sell,&a[2]);
pthread_create(&t4,NULL,sell,&a[3]);
pthread_join (t1,NULL);
pthread_join (t2,NULL);
pthread_join (t3,NULL);
pthread_join (t4,NULL);
return 0;
}
在 sell函数中 执行到if 条件为真后,开始sleep,这时其他线程可能进入,产生读脏数据。
另外
自增 自减操作本身并不是原子操作,
而是对应三条汇编指令
load 将共享变量 加载到 寄存器
update 更新寄存器的值 执行 + 1 / -1 操作
store 将新值从寄存器更新到共享变量的内存地址当中
解决产生读脏数据的思路就是 保证一个线程进入临界区后,其他线程不能再进入了。
方法:加锁
Linux 将这把锁称为互斥量
来看 加了锁的效果
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <sched.h>//cpu 控制
int ticket = 100;
pthread_mutex_t mutex;//define
void* sell(void * arg)
{
char** id = (char**) arg;
while(1)
{
pthread_mutex_lock(&mutex);//LOCK
if(ticket > 0)
{
usleep(1000);//模拟购票过程 //这样会导致 并发执行的出错 ,即线程切换,出现的读脏数据,解决策略是 加锁
printf("thread %s 购票成功,当前余量%d\n",*id,--ticket);
pthread_mutex_unlock(&mutex);
//sched_yield();//让优先级大于本线程的线程执行
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
return NULL;
}
int main ()
{
pthread_t t1,t2,t3,t4;
pthread_mutex_init(&mutex,NULL);// init lock
std::string a[4]={"thread 1","thread 2","thread 3","thread 4"};
pthread_create(&t1,NULL,sell,&a[0]);
pthread_create(&t2,NULL,sell,&a[1]);
pthread_create(&t3,NULL,sell,&a[2]);
pthread_create(&t4,NULL,sell,&a[3]);
pthread_join (t1,NULL);
pthread_join (t2,NULL);
pthread_join (t3,NULL);
pthread_join (t4,NULL);
pthread_mutex_destroy(&mutex);// destroy lock
return 0;
}
线程安全:一段代码被多个线程并发执行,不会出现不同的结果。(全局变量,静态变狼比较常见)
重入:重入是对函数而言,当一个执行流进入函数还没出函数时,其他执行流也进入;
这时若结果不会出现差错,称为可重入函数;否则,称为不可重入函数。
线程同步
简单例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_cond_t cond; //define
pthread_mutex_t mutex;
void* r1(void* arg)
{
(void) arg;
while(1)
{
pthread_cond_wait(&cond,&mutex);//等待条件满足 再执行
printf("活动\n");
}
return NULL;
}
void* r2(void* arg)
{
(void) arg;
while(1)
{
pthread_cond_signal(&cond);//唤醒等待
sleep(1);
}
return NULL;
}
int main()
{
pthread_t t1, t2;
pthread_cond_init(&cond,NULL);//初始化 cond 条件变量
pthread_mutex_init(&mutex,NULL);//初始化 lock
pthread_create(&t1,NULL,r1,NULL);
pthread_create(&t2,NULL,r2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);//释放锁
pthread_cond_destroy(&cond);//释放条件变量
return 0;
}
同步: 在保证安全前提下,能让线程按特定顺序访问临界资源,从而避免饥饿问题。
竞态条件:因时许问题导致程序异常的情况。
常用函数
1 条件变量初始化函数
pthread_cond_init(pthread_cond_t* cond,NULL);
2 destroy:
pthread_cond_destroy(pthread_cond_t* cond);
3 等待条件满足:
pthread_cond_wait(pthread_cond_t* cond , pthread_mutex_t mutex );
4 唤醒等待:
pthread_cond_broadcast(pthread_cond_t* cond );
pthread_cond_signal(pthread_cond_t* cond);
5 不唤醒等待,pthread_cond_wait()函数将陷入等待
但是我们看起来 互斥量 mutex好像无关紧要呀???,那么 为什么需要它?
一: 条件不会无缘无故变得满足,必然牵扯到共享数据的变化,所以需要有互斥量(锁)来保证数据变化的原子性。,若没有互斥锁,将无法保证共享数据的修改和获取安全
二:条件等待是线程同步的一种手段,若只有一个线程,条件不满足时,一直等待下去都不会满足,这时就需要再来一个线程来改变共享变量,使条件满足,并友好地通知该线程,现在条件满足,可以执行了
所以说 pthread_cond-wait 做了三件事
1 释放锁,
2 修改共享数据
3 重新加锁
这样在设计pthread_cond_wait 时,我们就得考虑内部的实现顺序了 ,避免发生错失信号,
原则,先等待,再解锁, 先上锁 再修改,在解锁,再发信号,唤醒等待。
来看一个生产消费 ,阻塞队列
#include <iostream>
#include <stdlib.h>
#include <pthread.h>
#include <queue>
#include <unistd.h>
#define NUM 50
class BlockQueue
{
public:
bool isFull()
{
return q.size() == cap;
}
bool isEmpty()
{
return q.size() == 0;
}
void lockQueue()
{
pthread_mutex_lock(&lock);
}
void unLockQueue()
{
pthread_mutex_unlock(&lock);
}
void productWait()
{
pthread_cond_wait(&full,&lock);
}
void consumeWait()
{
pthread_cond_wait(&empty,&lock);
}
void NotifyConsume()
{
pthread_cond_signal(&full);
}
void NotifyProduct()
{
pthread_cond_broadcast(&empty);
}
BlockQueue(int cap_ = NUM)
:cap(cap_)
{
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&full,NULL);
pthread_cond_init(&empty,NULL);
}
void pushData(const int& data)
{
lockQueue();//加锁
while(isFull())//q 仓库满 陷入等待
{
NotifyConsume();//满的话就通知消费者前来消费
std::cout<<"queue isfull ,Notifyed consume data ,product stop,"<<std::endl;
productWait();//生产陷入等待,等待消费 ,消费之后 isfull不成立,这时push_
}
q.push(data);//q 仓库有货,通知消费者可以前来消费
NotifyConsume();
unLockQueue();//操作完成,解锁
}
void popData(int& data)//输出型参数
{
lockQueue();//首先加互斥锁
while(isEmpty())
{
NotifyProduct();//一旦没货,通知生产者生产
std::cout<<"q is empty,notify product,consume stop,"<<std::endl;
consumeWait();//消费陷入等待
}
data = q.front();
q.pop();
NotifyProduct();
unLockQueue();
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
private:
std::queue <int> q;//仓库//阻塞队列
int cap ;//容量
pthread_mutex_t lock;//互斥锁
pthread_cond_t full;//库满
pthread_cond_t empty;//库空
};
void* consume(void* arg)
{
BlockQueue* bqp = (BlockQueue*)arg;
int data;
while(1)
{
bqp->popData(data);
std:: cout<<"Consume data:"<< data<<std::endl;
sleep(2);
}
return NULL;
}
void* product(void* arg)
{
BlockQueue* bqp =(BlockQueue*)arg;
srand((unsigned long)time(NULL));
while(1)
{
int data =rand() % 50;
bqp -> pushData(data);
std::cout<<"product data "<<data<<std::endl;
sleep(2);
}
return NULL;
}
int main()
{
BlockQueue bq;//阻塞队列对象
pthread_t t1,t2,t3,t4,t5,t6,t7;
pthread_create(&t1,NULL,product,(void*)&bq);
pthread_create(&t2,NULL,product,(void*)&bq);
pthread_create(&t3,NULL,product,(void*)&bq);
pthread_create(&t4,NULL,product,(void*)&bq);
pthread_create(&t5,NULL,consume,(void*)&bq);
pthread_create(&t6,NULL,consume,(void*)&bq);
pthread_create(&t7,NULL,consume,(void*)&bq);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
pthread_join(t5,NULL);
pthread_join(t6,NULL);
pthread_join(t7,NULL);
return 0;
}
POSIX信号量
POSIX信号量 和System V信号量相同,都是用于同步操作,达到无冲突访问临界资源的目的,但POSIX信号量可以用于线程间同步,System V(共享内存,消息队列,信号量)只能用于进程间
头文件 #include <semaphore.h>
初始化 int sem_init(sem_t*sem,int pshared , unsigned int value)
pshared: 0 表示线程共享,非0表示进程共享
value 信号量初始值
销毁:
int sem_destroy(sem_t* sem);
等待信号量:
int sem_wait(sem_t *sem); 将信号量值 -1
发布信号量;资源使用完毕,可以归还了
int sem_post(sem_t *sem);信号量值 +1
POSIX 信号量
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#include <vector>
#include <unistd.h>
#define NUM 16
class RingQueue
{
private:
std::vector<int>q;
int cap;
sem_t data_sem;
sem_t space_sem;
int consume_step;
int product_step;
public:
RingQueue(int _cap = NUM)
:q(_cap)//初始化 q的长度
,cap(_cap)
{
sem_init(&data_sem,0,0);//数据信号量, 0线程 0信号量初始值为 0
sem_init(&space_sem,0,cap);//空间信号量 0: 线程 ,0:初值0
consume_step=0;
product_step=0;
}
void PutData(const int& data)
{
sem_wait(&space_sem);//申请资源 //等待信号量,等待被唤醒 ,执行后续
q[consume_step] = data;
consume_step++;
consume_step %= cap;//循环操作
sem_post(&data_sem); //v 释放资源 //发布信号量
}
void GetData(int& data)
{
sem_wait(&data_sem); // P //等待信号量
data = q[product_step];
product_step++;
product_step %= cap;
sem_post(&space_sem);
}
~RingQueue()
{
sem_destroy(&data_sem);
sem_destroy(&space_sem);
}
};
void* consume(void* arg)
{
RingQueue *rqp = (RingQueue*)arg;
int data ;
while(1)
{
rqp->GetData(data);
std::cout<<"consume data"<<data <<std::endl;
sleep(1);
}
return NULL;
}
void* product(void* arg)
{
RingQueue *rqp =(RingQueue*) arg;
srand((unsigned long) time(NULL));
int data;
while(1)
{
data = rand() % 1024;
rqp->PutData(data);
std::cout<<"product data done: "<<data<<std::endl;
sleep(1);
}
return NULL;
}
int main()
{
pthread_t t1 ,t2;
RingQueue rq;
pthread_create(&t1,NULL,product,(void*)&rq );
pthread_create(&t2,NULL,consume,(void*)&rq );
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
线程池
线程池是线程的一种使用模式,目的是为了降低线程过多带来的开销问题,影响到局部性能和整体性能。
线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,设计线程池的主要目的是为了避免短时间线程的创建和销毁代价过大。
线程池不仅能够保证内核的充分利用,还能防止过分调度,可用的线程数量取决于可用的并发处理器、处理器内核、内存网络套接字等的数量。
线程池的应用场景:
1 需要大量的线程来完成任务,并且完成任务的时间比较短(相对于创建线程资源的时间来对比)
2对性能要求苛刻的应用,比如要求服务器快速响应客户请求的场景
3 能接受处理大量的突发性请求,但不至于是服务器产生大量的线程的应用。因为短时间内产生大量的线程可能导致内存达到极限,从而出错。
线程池简单例子:
#define __M_POOL_H__
#include <iostream>
#include <queue>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define MAX_THREAD 3 //最大线程数
#define MAX_QUEUE 10 //队列长度
class MyTask
{
private:
int data;
public:
MyTask(){}
~MyTask(){}
void SetData(int _data)
{
data =_data;
}
void Run()
{
int t = rand()% 5;
printf("thread : %lu sleep %d sec ,execute data : %d\n",pthread_self(),t,data);
sleep(1);
}
};
class MyThreadPool
{
private:
int max_thread;//线程池中最大线程数
int cur_thread;//当前线程数
int keep_running;//用于停止线程池中线程的标志
int max_queue;//队列中最大节点数
pthread_t* tid;//线程id
std::queue<MyTask*> task_list;//任务列表
pthread_mutex_t mutex; //互斥量
pthread_cond_t empty;//条件
pthread_cond_t full;//条件
void ThreadLock()
{
pthread_mutex_lock(&mutex);
}
void ThreadUnLock()
{
pthread_mutex_unlock(&mutex);
}
void ConsumeWait()
{
pthread_cond_wait(&empty,&mutex);
}
void ProductWait()
{
pthread_cond_wait(&full,&mutex);
}
void ConsumeNotify()
{
pthread_cond_signal(&empty);
}
void ProductNotify()
{
pthread_cond_broadcast(&full);
}
bool ThreadIsRunning()
{
return keep_running ==0;
}
void ThreadExit()
{
cur_thread--;//当前线程数-1
printf("thread :%lu exit\n",pthread_self());
// 通知主线程线程池中有线程退出
ProductNotify();
//释放线程资源
pthread_exit(NULL);
}
bool QueueIsEmpty()
{
return task_list .size() == 0;
}
bool QueueIsFull()
{
return task_list.size() == max_queue;
}
void PopTask(MyTask** task)
{
*task = task_list.front();
task_list.pop();
}
void PushTask(MyTask* task)
{
task_list.push(task);
}
static void* thread_routine(void *arg)
{
MyThreadPool *pthreadpool = (MyThreadPool*) arg;
while(1)
{
pthreadpool ->ThreadLock();
//如果当前是运行状态, 并且没有任务则挂起等待
while(pthreadpool->QueueIsEmpty() && pthreadpool->ThreadIsRunning())
pthreadpool->ConsumeWait();
//若果当前不是运行状态,并且没有任务,那么退出线程
//非运行 队列中有任务则需要将任务执行完毕后才可退出
if(! pthreadpool->ThreadIsRunning() && pthreadpool->QueueIsEmpty() )
{
pthreadpool->ThreadUnLock();
pthreadpool->ThreadExit();
}
//能执行到这里,说明Queue任务队列 不为空
MyTask *task;
pthreadpool->PopTask(&task);
pthreadpool->ProductNotify();
pthreadpool->ThreadUnLock();
//不能在锁内执行任务,否则导致其他线程饥饿
task->Run();
}
return NULL;
}
public:
MyThreadPool(int _max_thread = MAX_THREAD,int _max_queue = MAX_QUEUE)
:max_thread(_max_thread)
,cur_thread(_max_thread)
,max_queue(_max_queue)
,keep_running(1)
{
int i =0;
printf("create thread %d-%d\n",max_thread,max_queue);
tid = (pthread_t*)malloc(sizeof(pthread_t)* _max_thread);
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&empty,NULL);
pthread_cond_init(&full,NULL);
// 创建笃定数量的线程 等待执行任务
//
for(i=0;i<_max_thread;++i)
{
int ret = pthread_create(&tid[i],NULL,thread_routine,(void*)this);
if(ret != 0)
{
printf("create thread errorn\n");
exit(0);
}
pthread_detach(tid[i]);
}
}
~MyThreadPool()
{
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
bool AddTaskToPool(MyTask* task)
{
ThreadLock();
while(QueueIsFull())
{
ProductWait();
}
PushTask(task);
printf("add task to pool\n");
ConsumeNotify();
ThreadUnLock();
return true;
}
void StopThreadPool()
{
//如果已经调用过线程池退出,那么返回
if(keep_running == 0)
return ;
ThreadLock();
keep_running = 0;
//如果还有线程 没退出,挂起等待,直到所有线程执行完毕后退出
while(cur_thread > 0)
{
ProductWait();
}
ThreadUnLock();
}
};
int main()
{
MyTask task[10];
int i;
MyThreadPool pool;
for(i=0; i <10; ++i)
{
task[i].SetData(i);
pool.AddTaskToPool(&task[i]);
}
pool.StopThreadPool();
return 0;
}