多线程
初识线程
线程的概念
在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。
一切进程至少都有一个执行线程
线程在进程内部运行,本质是在进程地址空间内运行
在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
线程的优缺点
优点
创建一个新线程的代价要比创建一个新进程小得多。
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
线程占用的资源要比进程少很多。
能充分利用多处理器的可并行数量。
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
缺点
1.频繁的线程切换会导致系统运行效率降低。
2.健壮性(鲁棒性)相对于进程来说低。因为进程之间是独立的,而线程不具有这样的特性。一个线程崩溃,随之而来的便是整个进程的崩溃。
3.多个线程在访问同一变量时,可能会造成二义性问题。
了解pid(轻量级线程号)与tgid(线程组id)
tgid为进程号,pid为线程号
Lwp为轻量级进程(线程),相比于多进程来说,轻量级进程可以与主进程共用一份地址空间,与普通进程相比,LWP与其它进程共享所有(或大部分)逻辑地址空间和系统资源,一个进程可以创建多个LWP,这样它们共享大部分资源;LWP有它自己的进程标识符。
进程与线程概念解释
进程:是承担分配资源的基本实体,
线程:是调度的一个基本单位,线程是进程里面的一个执行流。
同一所属组下多线程之间共享与独立的空间
进程与线程的对比
1.从性质上来说:进程是承担资源分配的基本单位,而线程是cpu调度的一个基本单位
2.从特性上来说,进程具有独立性,而线程不具有独立性。
多进程与多线程对比
线程控制
线程的创建
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 using namespace std;
5 void *run(void *arg)
6 {
7 int *p=(int*)arg;
8 while(1)
9 {
10 cout<<"i am thread1,~~~~~"<<*p<<endl;
11 sleep(1);
12 }
13 }
14 int main()
15 {
16 pthread_t tid;
17 int i=10;
18 int ret=pthread_create(&tid,NULL,run,(void*)&i);
19 if(ret!=0)
20 {
21 return -1;
22 }
23 while(1)
24 {
25 cout<<"i am main thread"<<endl;
26 sleep(1);
27 }
28 return 0;
29 }
makefile
1 mythread:test_creat.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f mythread
线程入口的参数传递规则:
规则1:线程入口的参数不可以传递临时变量
规则2:传递的要是堆上开辟的空间,哪个线程用,哪个线程在用完后释放。
规则3:线程入口参数既可以传递内置类型,也可以传递自定义类型。
1 #include<iostream>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<pthread.h>
5 using namespace std;
6 class mythread
7 {
8 public:
9 mythread(int key)
10 {
11 i=key;
12 }
13 ~mythread()
14 {}
15 void start()
16 {
17 int ret= pthread_create(&tid,NULL,run,this);
18 if(ret<0)
19 {
20 return ;
21 }
22 }
23 static void*run(void *arg)
24 {
25 mythread*pn=(mythread*)arg;
26 while(1)
27 {
28
29 cout<<"i am thread "<<pn->i<<endl;
30 sleep(1);
31 }
32 }
33
34 private:
35 pthread_t tid;
36 int i;
37 };
38 int main()
39 {
40 mythread *rd=new mythread(5);
41 rd->start();
42 while(1)
43 {
44 cout<<" i am main thread"<<endl;
45 sleep(1);
46 }
47 delete rd;
48 return 0;
49 }
这个代码其实不是很好,因为没有考虑到让工作线程去释放。所以只供参考,不推荐写这样的代码
线程终止
常见线程退出的几种方式:
1: exit_thread.cpp ? ? ?? buffers
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 using namespace std;
6 struct student
7 {
8 void set(int num)
9 {
10 age=num;
11 }
12 int age;
13 };
14 void *run(void *arg)
15 {
16 student *pn=(student*)arg;
17 int count=0;
18 while(1)
19 {
20 if(count++==20)
21 {
22 // pthread_cancel(pthread_self());
23 // pthread_exit(NULL);
24 cout<<"我溜了,886"<<endl;
25 return NULL;
26 }
27 cout<<"i am work pthread "<< pn->age<<endl;
28 }
29 }
30 int main()
31 {
32
33 pthread_t tid;
34 student *pre=new student;
35 pre->set(20);
36 pthread_create(&tid,NULL,run,pre);
37 while(1)
38 {
39 cout<<"i am main thread"<<endl;
40 sleep(1);
41 }
42 return 0;
43 }
makefile
1 exit_thread:exit_thread.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f exit_thread
线程等待
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 using namespace std;
6 struct student
7 {
8 void set(int num)
9 {
10 age=num;
11 }
12 int age;
13 };
14 void *run(void *arg)
15 {
16 student *pn=(student*)arg;
17 int count=0;
18 while(1)
19 {
20 sleep(1); //先让它睡1s,发现主线程仍旧不打印,证明为阻塞等
21 if(count++==20)
22 {
23 // pthread_cancel(pthread_self());
24 // pthread_exit(NULL);
25 cout<<"我溜了,886"<<endl;
26 return NULL;
27 }
28 cout<<"i am work pthread "<< pn->age<<endl;
29 }
30 }
31 int main()
32 {
33
34 pthread_t tid;
35 student *pre=new student;
36 pre->set(20);
37 pthread_create(&tid,NULL,run,pre);
38 pthread_join(tid,NULL);
39 while(1)
40 {
41 cout<<"i am main thread"<<endl;
42 sleep(1);
43 }
44 return 0;
45 }
线程分离
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 using namespace std;
6 struct student
7 {
8 void set(int num)
9 {
10 age=num;
11 }
12 int age;
13 };
14 void *run(void *arg)
15 {
//自身分离
pthread_detach(pthread_self());
16 student *pn=(student*)arg;
17 int count=0;
18 while(1)
19 {
20 sleep(1); //先让它睡1s,发现主线程仍旧不打印,证明为阻塞等
21 if(count++==20)
22 {
23 // pthread_cancel(pthread_self());
24 // pthread_exit(NULL);
25 cout<<"我溜了,886"<<endl;
26 return NULL;
27 }
28 cout<<"i am work pthread "<< pn->age<<endl;
29 }
30 }
31 int main()
32 {
33
34 pthread_t tid;
35 student *pre=new student;
36 pre->set(20);
37 pthread_create(&tid,NULL,run,pre);
38 //pthread_join(tid,NULL);
//在主线程中设置分离属性
pthread_detach(tid);
39 while(1)
40 {
41 cout<<"i am main thread"<<endl;
42 sleep(1);
43 }
44 return 0;
45 }
线程安全
示例:
线程互斥:
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界区起保护作用
为什么需要?
假设有两个线程同时进入临界区去访问临界资源时,假如两个线程都想对这个变量做一个++操作的话,此时因为他们同时拿到了这份资源,在执行完++操作后,本该两个执行流的++操作,其实最后只起到了一个执行流的效果。所以,当我们对临界区中的临界资源进行修改时,必须保证其原子性。而且必须保证拥有临界资源的执行流有且只有一个,这样可以避免运算结果的二义性。
1 #include<iostream>
2 #include<stdio.h>
3 #include<unistd.h>
4 #include<pthread.h>
5 using namespace std;
6 const int num=4;
7 int ticket=1000;
W> 8 void * getticket(void *arg)
9 {
10
11 while(1)
12 {
13 if(ticket>0)
14 {
W> 15 printf("i am %p , i got a ticket %d\n",pthread_self(),ticket);
16 --ticket;
17 }
18 else
19 {
20 break;
21 }
22 }
W> 23 }
24 int main()
25 {
26 pthread_t arr[num];
27 int i=0;
28 for(;i<num;++i)
29 {
30 int ret= pthread_create(&arr[i],NULL,getticket,NULL);
31 if(ret<0)
32 {
33 cout<<"create error"<<endl;
34 return -1;
35 }
36 }
37 i=0;
38 for(;i<num;++i)
39 {
40 pthread_join(arr[i],NULL);
41 }
42 return 0;
43 }
两个不同的人拿到了同一张票,这便是线程不安全的情况
锁的本质
加锁的时机
加锁的接口
利用加锁解决掉上面拿到同一张票问题
1 #include<iostream>
2 #include<stdio.h>
3 #include<unistd.h>
4 #include<pthread.h>
5 using namespace std;
6 const int num=4;
//定义一个全局变量的锁
7 pthread_mutex_t lock;
8 int ticket=1000;
W> 9 void * getticket(void *arg)
10 {
11
12 while(1)
13 {
//上锁
14 pthread_mutex_lock(&lock);
15 if(ticket>0)
16 {
W> 17 printf("i am %p , i got a ticket%d\n",pthread_self(),ticket);
18 --ticket;
19 }
20 else
21 {
//确保在退出之前归还锁
22 pthread_mutex_unlock(&lock);
23 break;
24 }
//取完一次后也要归还锁
25 pthread_mutex_unlock(&lock);
26 }
27
W> 28 }
29 int main()
30 {
31 pthread_t arr[num];
32 int i=0;
//锁的初始化,动态初始化的,所以必须要释放该锁资源
33 pthread_mutex_init(&lock,NULL);
34 for(;i<num;++i)
35 {
36 int ret=pthread_create(&arr[i],NULL,getticket,NULL);
37 if(ret<0)
38 {
39 cout<<"create error"<<endl;
40 return -1;
41 }
42 }
43 i=0;
44 for(;i<num;++i)
45 {
46 pthread_join(arr[i],NULL);
47 }
//销毁该锁
48 pthread_mutex_destroy(&lock);
49 return 0;
50 }
加锁所带来的弊端
死锁
先构建出一个死锁情况
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5 pthread_mutex_t lock1;
6 pthread_mutex_t lock2;
7
W> 8 void *run1(void *arg)
9 {
10 pthread_mutex_lock(&lock1);
11 sleep(3);
12 pthread_mutex_lock(&lock2);
W> 13 }
W> 14 void *run2(void *arg)
15 {
16 pthread_mutex_lock(&lock2);
17 sleep(3);
18 pthread_mutex_lock(&lock1);
19
W> 20 }
21 int main()
22 {
23 pthread_mutex_init(&lock1,NULL);
24 pthread_mutex_init(&lock2,NULL);
25 pthread_t mid1,mid2;
26 pthread_create(&mid1,NULL,run1,NULL);
27 pthread_create(&mid2,NULL,run2,NULL);
28 pthread_join(mid1,NULL);
29 pthread_join(mid2,NULL);
30 while(1)
31 {
32 sleep(1);
33 }
34 pthread_mutex_destroy(&lock1);
35 pthread_mutex_destroy(&lock2);
36 }
根据gdb理解死锁
死锁的概念
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态
线程产生的必要条件
互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
避免死锁
1,资源一次性分配。
2,避免锁未释放的场景
3.加锁顺序一致。
线程同步
接口说明
初始化接口
等待接口
为什么第二个参数要传入锁?
唤醒接口
释放接口
int pthread_cond_destroy(pthread_cond_t *cond);
将初始化的条件变量释放。
基于同步实现生产者与消费者模型
优势:
阻塞队列
头文件:
1 #pragma once
2 #include<iostream>
3 #include<pthread.h>
4 #include<queue>
5 #include<unistd.h>
6 class task
7 {
8 public:
9 int x,y;
10 public:
11 task()
12 {}
13 task(int _x,int _y):x(_x),y(_y)
14 {}
15 int run()
16 {
17 return x+y;
18 }
19 ~task()
20 {
21
22 }
23
24 };
25 class Block_queue
26 {
27 private:
28 std::queue<task>t;//运行队列
29 size_t cap;//容量
30 pthread_mutex_t lock;//设置一把互斥锁
31 pthread_cond_t consumer;//消费者在该条件下等
32 pthread_cond_t productor;//生产者在该条件下等
33 public:
34 void LockQueue()
35 {
36 pthread_mutex_lock(&lock);
37 }
38 void UnlockQueuue()
39 {
40 pthread_mutex_unlock(&lock);
41 }
42 bool isfull()
43 {
44 return t.size()==cap;
45 }
46 bool isempty()
47 {
48 return t.size()==0;
49 }
50 void WakeConsumer()
51 {
52 std::cout<<"Wake up consumer"<<std::endl;
53 pthread_cond_signal(&consumer);
54 }
55 void WakeProductor()
56 {
57 std::cout<<"Wake up productor"<<std::endl;
58 pthread_cond_signal(&productor);
59 }
60 void ProductorWait()
61 {
62 std::cout<<"productor wait"<<std::endl;
63 pthread_cond_wait(&productor,&lock);
64 }
65 void ConsumerWait()
66 {
67 std::cout<<"consumer wait"<<std::endl;
68 pthread_cond_wait(&consumer,&lock);
69 }
70 public:
71 Block_queue(size_t _cap)
72 {
73 cap=_cap;
74 pthread_mutex_init(&lock,NULL);//初始化该锁
75 pthread_cond_init(&consumer,NULL);
76 pthread_cond_init(&productor,NULL);
77 }
78 void put(task t1)
79 {
80 LockQueue();
81 while(isfull())
82 {
83 WakeConsumer();
84 ProductorWait();
85 }
86 t.push(t1);
87 UnlockQueuue();
88 }
89 void get(task &t1)
90 {
91 LockQueue();
92 while(isempty())
93 {
94 WakeProductor();
95 ConsumerWait();
96 }
97 t1=t.front();
98 t.pop();
99 UnlockQueuue();
100 }
101 ~Block_queue()
102 {
103 pthread_mutex_destroy(&lock);
104 pthread_cond_destroy(&consumer);
105 pthread_cond_destroy(&productor);
106 }
107 };
main函数
1 #include"c_p.hpp"
2 #include<stdlib.h>
3 using namespace std;
4 pthread_mutex_t lock1,lock2;//lcok1为生产者锁,lock2为消费者锁
5 void* pro_run(void*arg)
6 {
7 Block_queue *rq=(Block_queue*)arg;
8 while(1)
9 {
10 //确保生产者先生产满
11 sleep(1);
12 //加的是生产者的锁
13 pthread_mutex_lock(&lock1);
14 int x=rand()%20+1;
15 int y=rand()%30+1;
16 task t(x,y);
17 rq->put(t);
18 cout<<"productor send task"<<x<<"+"<<y<<" = ?"<<endl;
19 pthread_mutex_unlock(&lock1);
20 sleep(1);
21 }
22 }
23 void *cu_run(void *arg)
24 {
25 Block_queue *rq=(Block_queue*)arg;
26 while(1)
27 {
28 pthread_mutex_lock(&lock2);
29 task t;
30 rq->get(t);
31 cout<<"consumer get task"<<t.x<<"+"<<t.y<<"= "<<t.run()<<endl;
32 pthread_mutex_unlock(&lock2);
33 }
34 }
35 int main()
36 {
37 pthread_t p1,p2,p3;//3个生产者
38 pthread_t c1,c2,c3;//3个消费者
39 //开辟一块新空间,这个空间中最多容纳5个元素
40 Block_queue *rq=new Block_queue(5);
41 //初始化锁
42 pthread_mutex_init(&lock1,NULL);
43 pthread_mutex_init(&lock2,NULL);
44 //3个生产者线程
45 pthread_create(&p1,NULL,pro_run,(void*)rq);
46 pthread_create(&p2,NULL,pro_run,(void*)rq);
47 pthread_create(&p3,NULL,pro_run,(void*)rq);
48 //3个消费者线程
49 pthread_create(&c1,NULL,cu_run,(void*)rq);
50 pthread_create(&c2,NULL,cu_run,(void*)rq);
51 pthread_create(&c3,NULL,cu_run,(void*)rq);
52 pthread_join(p1,NULL);
53 pthread_join(p2,NULL);
54 pthread_join(p3,NULL);
55 pthread_join(c1,NULL);
56 pthread_join(c2,NULL);
57 pthread_join(c3,NULL);
58 delete rq;
59 return 0;
60 }
makefile:
1 main:mymain.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f main
运行结果
POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
接口说明
初始化接口
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
信号量销毁
int sem_destroy(sem_t *sem);
等待信号量
p操作
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
归还信号量
v操作
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem)
基于环形队列实现
1 #pragma once
2 #include<iostream>
3 #include<unistd.h>
4 #include<semaphore.h>
5 #include<vector>
6 #define NUM 10
7 class Ring_Queue
8 {
9 private:
10 int cap;
11 std::vector<int>p;
12 int c_index;//生产者下标
13 int p_index;//消费者下标
sem_t lock;//加锁,
14 sem_t sem_blank;//对应空格字
15 sem_t sem_data;//格子中的数据
16 public:
17 void P(sem_t &t)
18 {
19 sem_wait(&t);
20 }
21 void V(sem_t &t)
22 {
23 sem_post(&t);
24 }
25 public:
W> 26 Ring_Queue(int _cap=NUM):p(_cap),cap(_cap)
27 {
28 sem_init(&sem_blank,0,NUM);//初始化生产者。默认情况下有 NUM个空各自
29 sem_init(&sem_data,0,0);//初始化消费者,默认情况下0个
sem_init(&lock,0,1);//默认0/1起到加锁作用,读写只能一个
30 c_index=0;
31 p_index=0;
32 }
33 void Put(const int &t)
34 {
35 P(sem_blank);
sem_wait(lock);
36 p[p_index]=t;
37 p_index++;
38 p_index%=NUM;
39 V(sem_data);
sem_post(lock);
40 }
41 void Get(int &t)
42 {
43 P(sem_data);//必须判断有空间才可以拿锁,要不然拿锁后直接被放在等待队列中,此时假如说读线程想要进行读的话,此时它申请不到锁,就会造成死锁现象
sem_wait(lock);
44 t=p[c_index];
45 c_index++;
46 c_index%=NUM;
47 V(sem_blank);
sem_post(lock);
48 }
49 ~Ring_Queue()
50 {
51 sem_destroy(&sem_blank);
52 sem_destroy(&sem_data);
sem_destroy(lock);
53 c_index=0;
54 p_index=0;
55 }
56
57 };
main
1 #include"ring.hpp"
2 void *productor(void *arg)
3 {
4 Ring_Queue *rq=(Ring_Queue*)arg;
5 int count=0;
6 while(1)
7 {
8 rq->Put(count);
9 count++;
10 if(count>10)
11 {
12 count=0;
13 }
14 std:: cout<<"productor over"<<std::endl;
15 }
16 }
17 void * consumer(void *arg)
18 {
19 Ring_Queue *rq=(Ring_Queue*)arg;
20 while(1)
21 {
22 sleep(1);
23 int data;
24 rq->Get(data);
25 std::cout<<"consumer get a data "<<data<<std::endl;
26 }
27
28 }
29 int main()
30 {
31 Ring_Queue *rq=new Ring_Queue();
32 pthread_t c,p;
33 pthread_create(&c,NULL,consumer,rq);
34 pthread_create(&p,NULL,productor,rq);
35 pthread_join(c,NULL);
36 pthread_join(p,NULL);
37 return 0;
38 }
~
makefile
1 main:main.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f main
线程池
线程池代码
.hpp文件
1 #pragma once
2 #include<iostream>
3 #include<pthread.h>
4 #include<queue>
5 #include<unistd.h>
6 #include<stdlib.h>
7 #include<cmath>
8 #define NUM 6
9 class Task
10 {
11 private:
12 int base;
13 public:
14 Task(){}
15 Task(int _base):base(_base)
16 {}
17 void run()
18 {
19 std::cout<<"thread is ["<<pthread_self()<<"]"<<"process this task"<< "pow("<<base<<",2)is"<<pow(base,2)<<std::endl;
20 }
21 ~Task(){}
22 };
23 class Thread_pool
24 {
25 private:
26 std::queue<Task*>v;
27 int max_num;
28 pthread_mutex_t lock;
29 pthread_cond_t cond;
30 bool quit;
31 public:
32 void thread_wait()
33 {
34 pthread_cond_wait(&cond,&lock);
35 }
36 void LockQueue()
37 {
38 pthread_mutex_lock(&lock);
39 }
40 void UnlockQueue()
41 {
42 pthread_mutex_unlock(&lock);
43 }
44 static void*RUN(void *arg)
45 {
46 Thread_pool *this_p=(Thread_pool*)arg;
47 while(true)
48 {
49 this_p->LockQueue();
50 while(this_p->Is_Empty())
51 {
52 this_p-> thread_wait();
53 }
54 Task t;
55 this_p->Get(t);
56 this_p->UnlockQueue();
57 t.run();
58 }
59 }
60 bool Is_Empty()
61 {
62 return v.size()==0;
63 }
64 void Thread_Wakeup()
65 {
66 pthread_cond_signal(&cond);
67 }
68 void Threads_Wake()
69 {
70 pthread_cond_broadcast(&cond);
71 }
72 public:
73 Thread_pool(int _max=NUM):max_num(_max),quit(false)
74 {}
75 void Thread_init()
76 {
77 pthread_mutex_init(&lock,NULL);
78 pthread_cond_init(&cond,NULL);
79 pthread_t t;
80 int i=0;
81 for(;i<max_num;++i)
82 {
83 pthread_create(&t,NULL,RUN,this);
84 }
85 }
86 void Put( Task &in)
87 {
88 LockQueue();
89 v.push(&in);
90 UnlockQueue();
91 Thread_Wakeup();
92 }
93 void Get(Task &t)
94 {
95 Task*p=v.front();
96 t=*p;
97 v.pop();
98 }
99 void Thread_Quit()
100 {
101 if(!Is_Empty())
102 {
103 std::cout<<"queue is not empty,can not quit"<<std::endl;
104 return;
105 }
106 else
107 {
108 quit=true;
109 Threads_Wake();
110 }
111 }
112 ~Thread_pool()
113 {
114 pthread_mutex_destroy(&lock);
115 pthread_cond_destroy(&cond);
116 }
117
118 };
.cpp文件
1 #include"thread_pool.hpp"
2 int main()
3 {
4 Thread_pool *rq=new Thread_pool();
5 rq->Thread_init();
6
7 while(true)
8 {
9 int x=rand()%10+1;
10 Task t(x);
11 rq->Put(t);
12 sleep(1);
13 }
14 return 0;
15 }
~
~
makefile
1 thread_pool:main.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f thread_pool
线程池中的惊群问题
解释:简单地说:就是扔一块食物,所有鸽子来抢,但最终只一个鸽子抢到了食物。
对应多线程就是:假如此时来了一个任务的话,所有线程池中休眠的线程均被唤醒。在多进程/多线程等待同一资源时,也会出现惊群。即当某一资源可用时,多个进程/线程会惊醒,竞争资源。这就是操作系统中的惊群。
惊群效应所产生的坏处:
1.惊醒所有进程/线程,导致n-1个线程做了无效的调度,上下文切换,cpu瞬时增高(单个任务)
2.多个进程/线程争抢资源,所以涉及到同步问题,需对资源进行加锁保护,加解锁加大系统CPU开销。
正常的用法:
所有线程共用一个锁,共用一个条件变量
当pthread_cond_signal通知时,就可能会出现惊群
解决惊群的方法:
所有线程共用一个锁,每个线程有自已的条件变量(在初始化传入的第二个参数)
pthread_cond_signal通知时,定向通知某个线程的条件变量,不会出现惊群。