线程概念
曾经写的代码当中有没有线程呢
理解以前:
进程在内核当中就是一个task_struct,在该结构体当中的成员变量 pid被我们称之为进程号。
现在理解:
1.操作系统当中没有线程的概念,程序猿说的创建线程,本质上在1inux操作系统当中创建轻量级进程(lwp)所以轻量级进程等价线程。
有的!,曾经写的代码当中存在线程,就是执行mian函数的执行流,被称之为主线程;程序员创建的线程被称之为叫做“工作线程”
2. pid本质上是轻量级进程id,换句话说,就是线程ID
在task struct当中
pid_t pid; //轻量级进程id,也被称之为线程id,不同的线程拥有不同的pid
pid_t tgid; //轻量级进程组id,也被称之为进程id,一个进程当中的线程拥有相同的tgid
为什么进程概念的时候,说pid就是进程id?
因为主线程的pid和tgid相等
线程的共享与独有
独有:在进程虚拟地址空间的共享区当中
调用栈,寄存器,线程ID,errno,信号屏蔽字,调度优先级
共享:
文件描述符表,用户id,用户组id,信号处理方式,当前进程的工作目录
线程的优缺点:
优点:
- 多线程的程序,拥有多个执行流,“合理使用,可以提高程序的运行效率多线
- 程程序的线程切换比多进程程序快,付出的代价小(有些可以共享的数
据(全局变量)就能在线程切换的时候,不进行切换) - 可以充分发挥多核CPU并行的优势
计算密集型的程序,可以进行拆分,让不同的线程执行计算不一样的
事情(1+2+3+……)
I/0密集型的程序,可以进行拆分,让不同的线程执行不同的I/0操
作,可以不用串型运行,提高程序运行效率
缺点: - 编写代码的难度更加高
- 代码的(稳定性)鲁棒性要求更加高线程数量并不是越多越好(滑稽吃鸡)
- 缺乏访问控制,可能会导致程序产生二义性结果
- 一个线程崩溃,会导致整个进程退出。(滑稽吃鸡)
线程控制
g++ $^ -o $@ -lpthread -g
线程创建
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* m_thread_start(void* arg){
int* a=(int*)arg;
printf("i am woker%d\n",*a);
}
int main(){
pthread_t tid;
int i=5;
int ret=pthread_create(&tid,NULL,m_thread_start,(void*)&i);
printf("%d\n",ret);
sleep(5);
return 0;
}
改善
struct Data_i{
int i;
};
void* my_thread_start(void* arg){
struct Data_i* p = (struct Data_i*)arg;
while(1){
printf("i am work thread!: %d\n", p->i);
sleep(1);
}
delete p;
}
int main()
{
for(int i = 0; i < 5; i++){
struct Data_i* lp = new Data_i();
lp->i = i;
pthread_t tid;
int ret = pthread_create(&tid, NULL, my_thread_start, (void*)lp);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
while(1){
printf("i am main thread\n");
sleep(1);
}
查看线程
ps aux | grep my
pstack 进程号
top -H -p 进程号
结论
测试入口函数的传参
结论1:不要传递临时变量给线程的入口函数
结论2:如果给线程入口函数传递了一个从堆上开辟的空间,让线程自行释放
线程终止
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* start(void* arg){
while(1){
printf("i am child");
sleep(1);
}
}
int main(){
pthread_t tid;
//创建线程
int ret=pthread_create(&tid,NULL,start,NULL);
if(ret<0){
perror("pthread_create error");
return 0;
}
//线程中止
pthread_cancel(tid);
//主线程不退出
while(1){
sleep(5);
}
return 0;
}
获取线程标识符
void* my_thread_start(void* arg){
int count = 10;
while(count--){
printf("i am work thread\n");
sleep(1);
}
pthread_cancel(pthread_self());
while(1){
printf("i am work thread-2\n");
sleep(1);
}
}
int main(){
//1.创建工作线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, my_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
//3.主线程死循环不退出, 观察现象
while(1){
printf("i am main thread\n");
sleep(1);
}
return 0;
}
线程等待
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* start(void* arg){
int count=0;
while(count<10){
printf("i am child");
sleep(1);
count++;
}
return NULL;
}
int main(){
pthread_t tid;
//创建线程
int ret=pthread_create(&tid,NULL,start,NULL);
if(ret<0){
perror("pthread_create error");
return 0;
}
pthread_join(tid,NULL);
return 0;
}
线程分离
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void* my_thread_start(void* arg){
pthread_detach(pthread_self());
(void)arg;
int count = 50;
while(count--){
printf("i am work thread\n");
sleep(1);
}
return NULL;
}
int main(){
//1.创建工作线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, my_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
while(1){
sleep(1);
}
return 0;
}
线程不安全状态
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_ticket = 100000;
void* my_thread_start(void* arg){
//修改全局变量
while(g_ticket > 0){
printf("i am %p, cout g_i val is %d\n", pthread_self(), g_ticket);
g_ticket--;
}
return NULL;
}
int main(){
//1.创建线程
// 两个工作线程修改全局变量
pthread_t tid[2];
for(int i = 0; i < 2; i++){
int ret = pthread_create(&tid[i], NULL, my_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
//2. 主线程 (只要不退出就好)
for(int i = 0; i < 2; i++){
pthread_join(tid[i], NULL);
}
return 0;
}
同步与互斥
互斥
互斥的要做的事情:控制线程的访问时序。当多个线程能够同时访问到临界资源的时候,有可能会导致线程执行的结果产生二义性。
而互斥就是要保证多个线程在访问同一个临界资源,执行临界区代码的时候(非原子性性操作(线程可以被打断)),控制访问时序。让一个线程独占临界资源执行完,再让另外一个独占执行;
互斥锁
原理
互斥锁的本质就是0/1计数器,计数器的取值只能为0或者1
计数器的值为1∶表示当前线程可以获取到互斥锁,从而去访问临界资源计数器的值为0︰表示当前线程不可以获取到互斥锁,从而不能访问临界资源
需要理解的是:并不是说线程不获取互斥锁不能访问临界资源,而是程序猿需要在代码当中用同一个互斥锁,去约束多个线程。
否则线程A加锁访问,线程B访问临界资源之前不加锁,那也约束不了线程B
信号量的计数器当中如何保证原子性
为什么计数器当中的值从0变成1,或者从1变成0是原子性的呢?
直接使用寄存器当中的值和计数器内存的值交换,而交换是一条汇编指令就可以完成的
加锁的时候:寄存器当中的值设置为(0)
第一种情况:计数器的值为1,说明锁空闲,没有被线程加锁
交换情况(画图)
第二种情况:计数器的值为0,说明锁忙碌,被其他线程加锁拿走
交换情况(画图)
解锁的时候:寄存器当中的值设置为(1)
计数器的值为0,需要解锁,进行一步交换。
问题
1.什么是线程不安全
- 多个线程并发/并行运行的时候,会导致程序结果的二义性。
- 假设有两个线程,线程A,线程B,有一个CPU,两个线程同时想对全局变量i进行加加,i的初始值为10;
2.怎么解决(互斥锁)
加锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_ticket = 100;
pthread_mutex_t g_lock;
void* my_thread_start(void* arg){
//修改全局变量
pthread_mutex_lock(&g_lock);
while(g_ticket > 0){
printf("i am %p, cout g_i val is %d\n", pthread_self(), g_ticket);
g_ticket--;
}
return NULL;
}
int main(){
//0.初始化互斥锁
pthread_mutex_init(&g_lock, NULL);
//1.创建线程
// 两个工作线程修改全局变量
pthread_t tid[2];
for(int i = 0; i < 2; i++){
int ret = pthread_create(&tid[i], NULL, my_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
//2. 主线程 (只要不退出就好)
for(int i = 0; i < 2; i++){
pthread_join(tid[i], NULL);
}
return 0;
}
解锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_ticket = 100;
pthread_mutex_t g_lock;
void* my_thread_start(void* arg){
//修改全局变量
while(g_ticket>0){
pthread_mutex_lock(&g_lock);
if(g_ticket>0){
printf("i am %p, cout g_i val is %d\n", pthread_self(), g_ticket);
g_ticket--;
}
pthread_mutex_unlock(&g_lock);}
return NULL;
}
int main(){
//0.初始化互斥锁
pthread_mutex_init(&g_lock, NULL);
//1.创建线程
// 两个工作线程修改全局变量
int i;
pthread_t tid[2];
for( i= 0; i < 2; i++){
int ret = pthread_create(&tid[i], NULL, my_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
//2. 主线程 (只要不退出就好)
for( i = 0; i < 2; i++){
pthread_join(tid[i], NULL);
}
return 0;
}
销毁锁
同步
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_COUNT 1
//代表碗: 0表示没有面, 1表示有面
int g_bowl = 0;
pthread_mutex_t g_lock;
void* eat_thread_start(void* arg){
while(1){
pthread_mutex_lock(&g_lock);
if(g_bowl == 0){
printf("我是吃面人, 碗里面没有面了, 我就不吃了....\n");
pthread_mutex_unlock(&g_lock);
continue;
}
printf("碗里面有面, 我可以吃 : %d\n", g_bowl--);
//printf("i am eat thread: eat %d\n", g_bowl--);
pthread_mutex_unlock(&g_lock);
}
}
void* make_thread_start(void* arg){
while(1){
pthread_mutex_lock(&g_lock);
if(g_bowl == 1){
printf("我是做面人, 碗里面有面呢, 我就不做了...\n");
pthread_mutex_unlock(&g_lock);
continue;
}
printf("碗里面没有面了, 我可以做面了, %d\n", ++g_bowl);
//printf("i am make thread, make %d\n", ++g_bowl);
pthread_mutex_unlock(&g_lock);
}
}
int main(){
//1.初始化互斥锁
pthread_mutex_init(&g_lock, NULL);
//2.创建吃面的线程 和 做面的线程
pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
for(int i = 0; i < THREAD_COUNT; i++){
int ret = pthread_create(&eat[i], NULL, eat_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
ret = pthread_create(&make[i], NULL, make_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
//3.等待两种线程
for(int i = 0; i < THREAD_COUNT; i++){
pthread_join(eat[i], NULL);
pthread_join(make[i], NULL);
}
//4.销毁互斥锁
pthread_mutex_destroy(&g_lock);
return 0;
}
现在写的这个代码,非常耗费CPU资源,因为判断了即使资源没有准备充分,释放掉互斥锁之后,也极有可能这个互斥锁又被该线程拿到。
条件变量
条件变量的使用原理:
线程在加锁之后,判断下临界资源是否可用:
- 如果可用:则直接访间临界资源
- 如果不可用:则调用等待接口,让该线程进行等待
条件变量的原理
本质上是:PCB等待队列(存放在等待的线程的PCB)
条件变量接口
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_COUNT 1
//代表碗: 0表示没有面, 1表示有面
int g_bowl = 0;
pthread_mutex_t g_lock;
pthread_cond_t g_cond;
void* eat_thread_start(void* arg){
while(1){
pthread_mutex_lock(&g_lock);
if(g_bowl == 0){//改善将if改成while
printf("我是吃面人, 碗里面没有面了, 我就不吃了....\n");
pthread_cond_wait(&g_cond, &g_lock);
}
printf("碗里面有面, 我可以吃 : %d\n", g_bowl--);
//printf("i am eat thread: eat %d\n", g_bowl--);
pthread_mutex_unlock(&g_lock);
//通知做面的人做面
pthread_cond_signal(&g_cond);
}
}
void* make_thread_start(void* arg){
while(1){
pthread_mutex_lock(&g_lock);
if(g_bowl == 1){//改善将if改成while
printf("我是做面人, 碗里面有面呢, 我就不做了...\n");
pthread_cond_wait(&g_cond, &g_lock);
}
printf("碗里面没有面了, 我可以做面了, %d\n", ++g_bowl);
//printf("i am make thread, make %d\n", ++g_bowl);
pthread_mutex_unlock(&g_lock);
//通知吃面的人吃面
pthread_cond_signal(&g_cond);
}
}
int main(){
//1.初始化互斥锁
pthread_mutex_init(&g_lock, NULL);
pthread_cond_init(&g_cond, NULL);
//2.创建吃面的线程 和 做面的线程
pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
for(int i = 0; i < THREAD_COUNT; i++){
int ret = pthread_create(&eat[i], NULL, eat_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
ret = pthread_create(&make[i], NULL, make_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
//3.等待两种线程
for(int i = 0; i < THREAD_COUNT; i++){
pthread_join(eat[i], NULL);
pthread_join(make[i], NULL);
}
//4.销毁互斥锁
pthread_mutex_destroy(&g_lock);
pthread_cond_destroy(&g_cond);
return 0;
}
补充
防止死锁
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_COUNT 2
//代表碗: 0表示没有面, 1表示有面
int g_bowl = 0;
pthread_mutex_t g_lock;
pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;
void* eat_thread_start(void* arg){
while(1){
pthread_mutex_lock(&g_lock);
//while : 当吃面线程被唤醒之后, 应该再次判断下碗里面有没有面。
// 如果说有 : 那跳出while循环, 吃面
// 如果没有(有可能面被其他吃面线程先行吃掉了) : 那执行循环体, 继续等待
while(g_bowl == 0){
//printf("我是吃面人, 碗里面没有面了, 我就不吃了....\n");
pthread_cond_wait(&g_eat_cond, &g_lock);
}
printf("我是 %p, 碗里面有面, 我可以吃 : %d\n", pthread_self(), g_bowl--);
pthread_mutex_unlock(&g_lock);
//通知做面的人做面
pthread_cond_signal(&g_make_cond);
}
}
void* make_thread_start(void* arg){
while(1){
pthread_mutex_lock(&g_lock);
//while : 当被唤醒之后, 再次判断碗里是否有面
// 如果没有 : 那就做面
// 没有有(刚刚已经被另外一个做面的线程抢先做了): 不用做面, 执行循环体, 继续等待
while(g_bowl == 1){
//printf("我是做面人, 碗里面有面呢, 我就不做了...\n");
pthread_cond_wait(&g_make_cond, &g_lock);
}
printf("我是 %p, 碗里面没有面了, 我可以做面了, %d\n", pthread_self(), ++g_bowl);
pthread_mutex_unlock(&g_lock);
//通知吃面的人吃面
pthread_cond_signal(&g_eat_cond);
}
}
int main(){
//1.初始化互斥锁
pthread_mutex_init(&g_lock, NULL);
pthread_cond_init(&g_eat_cond, NULL);
pthread_cond_init(&g_make_cond, NULL);
//2.创建吃面的线程 和 做面的线程
pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
for(int i = 0; i < THREAD_COUNT; i++){
int ret = pthread_create(&eat[i], NULL, eat_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
ret = pthread_create(&make[i], NULL, make_thread_start, NULL);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
//3.等待两种线程
for(int i = 0; i < THREAD_COUNT; i++){
pthread_join(eat[i], NULL);
pthread_join(make[i], NULL);
}
//4.销毁互斥锁
pthread_mutex_destroy(&g_lock);
pthread_cond_destroy(&g_eat_cond);
pthread_cond_destroy(&g_make_cond);
return 0;
}
条件变量问题wait接口
ptrhead_cond_wait的内部是针对互斥锁做了什么操作?
结论:先放到PCB等待队列,再进行解锁线程被唤醒了之后会执行什么代码,
pthread_cond_wait函数在返回之前一定会在其内部进行加锁操作;
抢锁的时候:
1.抢到了,pthread_cond_wait函数就真正执行完毕了,函数返回。
⒉没抢到, pthread_cond_wait函数的代码就没有真正的执行完毕,还处于函数内部抢锁的逻辑当中,还会继续去抢锁,直到抢到互斥锁,才返回。
死锁
gdb死锁分析
gdb 分析运行程序
死锁的必要条件
死锁预防
生产者与消费者模型
规则
优点
- 忙闲不均
- 生产者与消费者解耦
- 支持高并发
代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <queue>
using namespace std;
#define THREAD_COUNT 2
/*
* 1.线程安全队列
* */
class RingQueue{
public:
RingQueue(){
capacity_ = 10;
pthread_mutex_init(&que_lock_, NULL);
pthread_cond_init(&cons_cond_, NULL);
pthread_cond_init(&prod_cond_, NULL);
}
~RingQueue(){
pthread_mutex_destroy(&que_lock_);
pthread_cond_destroy(&cons_cond_);
pthread_cond_destroy(&prod_cond_);
}
/*
* 1.提供给生产者线程使用的接口
* */
void Push(int data){
pthread_mutex_lock(&que_lock_);
while(que_.size() >= capacity_){
pthread_cond_wait(&prod_cond_, &que_lock_);
}
que_.push(data);
printf("i am product thread : %p, i product %d\n", pthread_self(), data);
pthread_mutex_unlock(&que_lock_);
pthread_cond_signal(&cons_cond_);
}
/*
* 1.提供给消费者线程进行消费的接口
* */
void Pop(int* data){
pthread_mutex_lock(&que_lock_);
while(que_.size() <= 0){
pthread_cond_wait(&cons_cond_, &que_lock_);
}
*data = que_.front();
que_.pop();
printf("i am consume thread : %p, i consume %d\n", pthread_self(), *data);
pthread_mutex_unlock(&que_lock_);
pthread_cond_signal(&prod_cond_);
}
private:
//队列
queue<int> que_;
size_t capacity_;
//互斥锁
pthread_mutex_t que_lock_;
//同步
pthread_cond_t cons_cond_;
pthread_cond_t prod_cond_;
};
void* cons_start(void* arg){
RingQueue* rq = (RingQueue*)arg;
while(1){
int data;
rq->Pop(&data);
}
}
int g_data = 0;
pthread_mutex_t g_data_lock = PTHREAD_MUTEX_INITIALIZER;
void* prod_start(void* arg){
RingQueue* rq = (RingQueue*)arg;
while(1){
pthread_mutex_lock(&g_data_lock);
rq->Push(g_data);
g_data++;
//sleep(1);
pthread_mutex_unlock(&g_data_lock);
}
}
int main(){
RingQueue* rq = new RingQueue();
pthread_t cons[THREAD_COUNT], prod[THREAD_COUNT];
for(int i = 0; i < THREAD_COUNT; i++){
int ret = pthread_create(&cons[i], NULL, cons_start, (void*)rq);
if(ret < 0){
perror("pthread_create");
return 0;
}
ret = pthread_create(&prod[i], NULL, prod_start, (void*)rq);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
for(int i = 0; i < THREAD_COUNT; i++){
pthread_join(cons[i], NULL);
pthread_join(prod[i], NULL);
}
delete rq;
return 0;
}
信号量
信号量的原理
资源计数器+PCB等待队列
先拿信号量再加锁
资源技术器:
执行流获取信号量,
- 获取成功,信号量计数器减1操作
- 获取失败,执行流放入到PCB等待队列
执行流释放信号量成功之后,计数器加1操作
信号量的接口
初始化接口
int sem_init(sem_t *sem,int pshared,unsigned int value);
sem :信号量,sem_t是信号量的类型
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等待队列,·因为没有线程在等待
暂停:讨论信号量用于同步和互斥的情况
互斥:资源计数器的取值为0/1
同步:资源计数器的取值不限于只能是0/1,表示有多少个资源可以使用
销毁接口
int sem destroy(sem_t *sem) ;
生产者消费者模式代码
#include <stdio.h>
#include <unistd.h>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
using namespace std;
/*
* 定义线程安全的队列
* 环形队列(用数组模拟)
* 线程安全:
* 同步:信号量
* 互斥: 信号量
* */
#define CAPACITY 1
class Rinqueue{
public:
Rinqueue():vec_(CAPACITY){
capacity_ = CAPACITY;
sem_init(&sem_lock_, 0, 1);
sem_init(&sem_cons_, 0, 0);
sem_init(&sem_prod_, 0, CAPACITY);
pos_write_ = 0;
pos_read_ = 0;
}
~Rinqueue(){
sem_destroy(&sem_lock_);
sem_destroy(&sem_cons_);
sem_destroy(&sem_prod_);
}
void Push(int data){
sem_wait(&sem_prod_);
sem_wait(&sem_lock_);
printf("i am product %p, i product %d\n", pthread_self(), data);
vec_[pos_write_] = data;
pos_write_ = (pos_write_ + 1) % capacity_;
sem_post(&sem_lock_);
sem_post(&sem_cons_);
}
void Pop(){
sem_wait(&sem_cons_);
sem_wait(&sem_lock_);
int data = vec_[pos_read_];
pos_read_ = (pos_read_ + 1) % capacity_;
printf("i am thread %p, i consume %d\n", pthread_self(), data);
sem_post(&sem_lock_);
sem_post(&sem_prod_);
}
private:
vector<int> vec_;
//数组的容量大小
size_t capacity_;
//保证互斥的信号量
sem_t sem_lock_;
//消费者的信号量
sem_t sem_cons_;
//生产者的信号量
sem_t sem_prod_;
int pos_write_;
int pos_read_;
};
/*
* 创建两种角色的线程
* 1.生产者
* 2.消费者
* */
#define THREADCOUNT 1
void* cons_strat(void* arg){
Rinqueue* rq = (Rinqueue*)arg;
while(1){
rq->Pop();
sleep(2);
}
}
int g_data = 0;
void* prod_strat(void* arg){
Rinqueue* rq = (Rinqueue*)arg;
while(1){
rq->Push(g_data++);
sleep(1);
}
}
int main(){
Rinqueue* rq = new Rinqueue();
pthread_t cons[THREADCOUNT], prod[THREADCOUNT];
for(int i = 0; i < THREADCOUNT; i++){
int ret = pthread_create(&cons[i], NULL, cons_strat, (void*)rq);
if(ret < 0){
perror("pthread_create");
return 0;
}
ret = pthread_create(&prod[i], NULL, prod_strat, (void*)rq);
if(ret < 0){
perror("pthread_create");
return 0;
}
}
for(int i = 0; i < THREADCOUNT; i++){
pthread_join(cons[i], NULL);
pthread_join(prod[i], NULL);
}
return 0;
}
线程池
前言
应用场景
1个线程在被创建之后,只能执行一个线程入口函数,-后续是没有办法
更改的。基于这种场景,线程可能执行的代码也就是固定了。换句话说即使线程入口函数当中有很多分支语句,可以有很多分支,.但是相对来说线程执行的路线都是固定的,要么是A分支,要么是B分支,要么是C分支…
业务类型越来越多
所以为了能给让线程执行不一样的业务代码就要考虑线程从队列当中获取的元素身上下功夫。让线程可以通过线程元素来执行不一样的代码。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <queue>
using namespace std;
typedef void (*Handler)(int data);
class QueueData{
public:
QueueData(){
}
QueueData(int data, Handler handler){
data_ = data;
handler_ = handler;
}
void run(){
handler_(data_);
}
private:
int data_;
Handler handler_;
};
class ThreadPool{
public:
ThreadPool(int capa, int thread_count){
capacity_ = capa;
pthread_mutex_init(&lock_, NULL);
pthread_cond_init(&cons_cond_, NULL);
thread_count_ = thread_count;
pthread_cond_init(&prod_cond_, NULL);
flag_exit_ = 0;
}
~ThreadPool(){
pthread_mutex_destroy(&lock_);
pthread_cond_destroy(&cons_cond_);
pthread_cond_destroy(&prod_cond_);
}
int OnInit(){
int cnt = 0;
for(int i = 0; i < thread_count_; i++){
pthread_t tid;
int ret = pthread_create(&tid, NULL, ThreadPollStart, (void*)this);
if(ret < 0){
cnt++;
}
}
return thread_count_ -= cnt;
}
void Push(QueueData qd){
pthread_mutex_lock(&lock_);
while(que_.size() >= capacity_){
if(flag_exit_){
pthread_mutex_unlock(&lock_);
return;
}
pthread_cond_wait(&prod_cond_, &lock_);
}
que_.push(qd);
pthread_mutex_unlock(&lock_);
pthread_cond_signal(&cons_cond_);
}
void Pop(QueueData* qd){
*qd = que_.front();
que_.pop();
}
static void* ThreadPollStart(void* arg){
pthread_detach(pthread_self());
ThreadPool* tp = (ThreadPool*)arg;
while(1){
//pos1 - no
pthread_mutex_lock(&tp->lock_);
while(tp->que_.empty()){
//pos2 - yes
if(tp->flag_exit_){
tp->thread_count_--;
pthread_mutex_unlock(&tp->lock_);
pthread_exit(NULL);
}
pthread_cond_wait(&tp->cons_cond_, &tp->lock_);
}
QueueData qd;
tp->Pop(&qd);
pthread_mutex_unlock(&tp->lock_);
pthread_cond_signal(&tp->prod_cond_);
qd.run();
}
return NULL;
}
void ThreadPoolExit(){
flag_exit_ = 1;
while(thread_count_ > 0){
pthread_cond_signal(&cons_cond_);
}
}
private:
queue<QueueData> que_;
size_t capacity_;
pthread_mutex_t lock_;
pthread_cond_t cons_cond_;
pthread_cond_t prod_cond_;
int thread_count_;
int flag_exit_;
};
void DealData(int data){
printf("data = %d\n", data);
}
int main(){
ThreadPool* tp = new ThreadPool(10, 5);
if(tp == NULL){
printf("create threadpool failed\n");
return 0;
}
if(tp->OnInit() <= 0){
printf("create thread failed\n");
return 0;
}
for(int i = 0; i < 10000; i++){
QueueData qd(i, DealData);
tp->Push(qd);
}
tp->ThreadPoolExit();
delete tp;
return 0;
}
乐观锁悲观锁
读写锁
自旋锁
白旋锁(busy-waiting类型)和互斥锁(sleep-waiting类型)的区别:
1.自旋锁加锁时,加不到锁,线程不会切换(时间片没有到的情况,时间片到了,也会线程切换〉会持续的尝试拿锁,直到拿到自旋锁
2.互斥锁加锁时,加不到锁,线程会切换(时间片没有到,也会切换),进入睡眠状态,“当其他线程释放互斥锁(解锁)之后,被唤醒。在切换回来,进行强锁
3.白旋锁的优点:因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。
4.白旋锁的缺点:自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低
5.适用于临界区代码较短时(直白的说:临界区代码执行时间短)的情况,使用自旋锁效率比较高。因为线程不用来回切换。6.当临界区当中执行时间较长,自旋锁就不适用了,因为拿不到锁会占用CPU一直抢占锁。