1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 pthread_detach(pthread_self());
10 while(1)
11 {
12 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
13 sleep(1);
14 break;
15 }
16 int a=10;
17 a=a/0;
18 return (void*)10;
19 }
20 int main()
21 {
22 pthread_t tid;
23 int ret=0;
24 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
25 if(ret!=0)
26 {
27 return -1;
28 }
29
30 sleep(10);
31 pthread_cancel(tid);
32 cout<<"new thread "<<tid<<" be cancled!"<<endl;
33 void* tmp=NULL;
34 pthread_join(tid,&tmp);
35 cout<<"thread qiut code:"<<(long long )ret<<endl;
36 return 100;
37 }
- int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
- pthread_t:线程标识符,本质上是线程在共享区独有空间的首地址。
-pthread_t:是一个出参,该值由pthread_creat函数赋值的。
-thread:创建线程的属性,一般情况都指定为NULL,采用默认属性。- pthread_attr_t:函数指针,接收一个返回值为void*,参数为void*的函数地址,就是线程入口函数。
- void *(*start_routine) (void *):给线程入口函数传递的参数;由于参数类型是void*,返回值类型为void*,所以给了程序无限的传递参数的方式(char*,int*,结构体指针,this)
- 返回值:
失败:< 0
在主线程中创建一个工作线程,主线程和副线程都不退出。
mythead.cpp
1 #include<iostream>
2 #include<pthread.h>
3 using namespace std;
4 #include<unistd.h>
5
6 void* thread_run(void* arg)
7 {
8 while(1)
9 {
10 cout<<"i am "<<(char*)arg<<endl;
11 sleep(1);
12 }
13 }
14 int main()
15 {
16 pthread_t tid;
17 int ret=pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
18 if(ret!=0)
19 {
20 return -1;
21 }
22 while(1)
23 {
24 cout<<"i am main thread"<<endl;
25 sleep(2);
26 }
27 return 0;
28 }
1 mythread:mythread.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f mythread
1 #include<iostream>
2 #include<pthread.h>
3 using namespace std;
4 #include<unistd.h>
5
6 void* thread_run(void* arg)
7 {
8 while(1)
9 {
10 cout<<"i am "<<(char*)arg<<endl;
11 sleep(1);
12 }
13 }
14 int main()
15 {
16 pthread_t tid;
17 int ret=pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
18 if(ret!=0)
19 {
20 return -1;
21 }
22 return 0;
23 }
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 using namespace std;
5
6 void* MyThreadStrat(void* arg)
7 {
8 int* i=(int*)arg;
9 while(1)
10 {
11 cout<<"MyThreadStrat:"<<*i<<endl;
12 sleep(1);
13 }
14 return NULL;
15 }
16 int main()
17 {
18 pthread_t tid;
19 int i=1;
20 int ret=pthread_create(&tid,NULL,MyThreadStrat,(void*)&i);
21 if(ret!=0)
22 {
23 cout<<"线程创建失败!"<<endl;
24 return 0;
25 }
26 while(1)
27 {
28 sleep(1);
29 cout<<"i am main thread"<<endl;
30 }
31 return 0;
32 }
虽然参数可以正常传入,但实际是存在一定的错误的,因为局部变量 i 传入的时候生命周期未结束,而在传递给工作线程的时候生命周期结束了,那么这块局部变量开辟的区域就会自动释放,而此时工作线程还在访问这块地址,就会出现非法访问。
代码改成循环:
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 using namespace std;
5
6 void* MyThreadStrat(void* arg)
7 {
8 int* i=(int*)arg;
9 while(1)
10 {
11 cout<<"MyThreadStrat:"<<*i<<endl;
12 sleep(1);
13 }
14 return NULL;
15 }
16 int main()
17 {
18 pthread_t tid;
19 int i=0;
20 for( i=0;i<4;i++)
21 {
22 int ret=pthread_create(&tid,NULL,MyThreadStrat,(void*)&i);
23 if(ret!=0)
24 {
25 cout<<"线程创建失败!"<<endl;
26 return 0;
27 }
28 }
29 while(1)
30 {
31 sleep(1);
32 cout<<"i am main thread"<<endl;
33 }
34 return 0;
35 }
因为for循环4次最终开辟4个工作线程,开辟线程传递进去的是 i 的地址,而 i 中的值从0加到4,而 i 到5退出,此时 i 已经被加为4,最终 i 的地址中存的值为 4,使用最终会一直输出4。
class MyThread
{
public:
MyThread()
{
}
~MyThread()
{
}
int Start()
{
int ret = pthread_create(&tid_, NULL, MyThreadStart, (void*)this);
if(ret < 0)
{
return -1;
}
return 0;
}
static void* MyThreadStart(void* arg)
{
MyThread* mt = (MyThread*)arg;
printf("%p\n", mt->tid_);
}
private:
pthread_t tid_;
};
int main()
{
return 0;
}
传递结构体指针:
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 using namespace std;
5
6 struct ThreadId
7 {
8 int thread_id;
9 };
10 void* MyThreadStrat(void* arg)
11 {
12 struct ThreadId* tid=(struct ThreadId*)arg;
13 while(1)
14 {
15 cout<<"MyThreadStrat:"<<tid->thread_id<<endl;
16 sleep(1);
17 }
18 delete tid;
19 }
20 int main()
21 {
22 pthread_t tid;
23 int i=0;
24 for( i=0;i<4;i++)
25 {
26 struct ThreadId* id=new ThreadId();
27 id->thread_id=i;
28 int ret=pthread_create(&tid,NULL,MyThreadStrat,(void*)id);
29 if(ret!=0)
30 {
31 cout<<"线程创建失败!"<<endl;
32 return 0;
33 }
34 }
35 while(1)
36 {
37 sleep(1);
38 cout<<"i am main thread"<<endl;
39 }
40 return 0;
41 }
1 #include<iostream>
2 #include<pthread.h>
3 using namespace std;
4 #include<sys/types.h>
5 #include<unistd.h>
6
7 void* thread_run(void* arg)
8 {
9 while(1)
10 {
11 cout<<"i am "<<(char*)arg<<"pid:"<<getpid()<<endl;
12 sleep(1);
13 }
14 }
15 int main()
16 {
17 pthread_t tid;
18 int ret=pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
19 if(ret!=0)
20 {
21 return -1;
22 }
23 while(1)
24 {
25 cout<<"i am main thread,pid:"<<getpid()<<endl;
26 sleep(2);
27 }
28 return 0;
29 }
ps命令中的-L选项,会显示如下信息:
LWP:线程ID,既gettid()系统调用的返回值。
从上面可以看出,进程的ID为12878,下面有一个线程的ID也是12878,这不是巧合。线程组内的第一个线程,在用户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,group_leader指针则指向自身,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。
至于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。
强调一点,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。
1 #include<iostream>
2 #include<pthread.h>
3 using namespace std;
4 #include<sys/types.h>
5 #include<unistd.h>
6
7 void* thread_run(void* arg)
8 {
9 while(1)
10 {
11 cout<<"i am:"<<(char*)arg<<"pid:"<<getpid()<<" "<<"my thread id is:"<<pthread_self()<<endl;
12 sleep(1);
13 }
14 }
15 int main()
16 {
17 pthread_t tid;
18 int ret=pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
19 if(ret!=0)
20 {
21 return -1;
22 }
23 while(1)
24 {
25 cout<<"i am main thread id:"<<pthread_self()<<" "<<"new thread:"<<tid<<" "<<"pid:"<<getpid()<<endl;
26 sleep(2);
27 }
28 return 0;
29 }
pthread_t到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 // while(1)
10 // {
11 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
12 sleep(1);
13 return (void*)10;
14 // }
15 }
16 int main()
17 {
18 pthread_t tid;
19 int ret=0;
20 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
21 if(ret!=0)
22 {
23 return -1;
24 }
25
26 while(1)
27 {
28 cout<<"i am main:"<<pthread_self()<<" pid:"<<getpid()<<endl;
29 sleep(2);
30 }
31 void* tmp=NULL;
32 pthread_join(tid,&tmp);
33 cout<<"thread eixt code:"<<(long long) tmp<<endl;
34 return 0;
35 }
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 // while(1)
10 // {
11 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
12 sleep(1);
13 pthread_exit((void*)10);
14 // }
15 }
16 int main()
17 {
18 pthread_t tid;
19 int ret=0;
20 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
21 if(ret!=0)
22 {
23 return -1;
24 }
25
26 while(1)
27 {
28 cout<<"i am main:"<<pthread_self()<<" pid:"<<getpid()<<endl;
29 sleep(2);
30 }
31 void* tmp=NULL;
32 pthread_join(tid,&tmp);
33 cout<<"thread quit code:"<<(long long)ret<<endl;
34 return 0;
35 }
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 while(1)
10 {
11 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
12 sleep(10);
13 }
14 }
15 int main()
16 {
17 pthread_t tid;
18 int ret=0;
19 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
20 if(ret!=0)
21 {
22 return -1;
23 }
24
25 sleep(10);
26 pthread_cancel(tid);
27 cout<<"new thread "<<tid<<" be cancled!"<<endl;
28 void* tmp=NULL;
29 pthread_join(tid,&tmp);
30 cout<<"thread qiut code:"<<(long long )ret<<endl;
31 return 0;
32 }
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 while(1)
10 {
11 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
12 sleep(10);
13 }
14 }
15 int main()
16 {
17 pthread_t tid;
18 int ret=0;
19 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
20 if(ret!=0)
21 {
22 return -1;
23 }
24
25 // while(1)
26 // {
27 cout<<"i am main:"<<pthread_self()<<" pid:"<<getpid()<<endl;
28 sleep(2);
29 // }
30 void* tmp=NULL;
31 pthread_join(tid,&tmp);
32 return 0;
33 }
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 pthread_detach(pthread_self());
10 while(1)
11 {
12 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
13 sleep(1);
14 break;
15 }
16 return (void*)10;
17 }
18 int main()
19 {
20 pthread_t tid;
21 int ret=0;
22 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
23 if(ret!=0)
24 {
25 return -1;
26 }
27
28 sleep(10);
29 pthread_cancel(tid);
30 cout<<"new thread "<<tid<<" be cancled!"<<endl;
31 void* tmp=NULL;
32 pthread_join(tid,&tmp);
33 cout<<"thread qiut code:"<<(long long )ret<<endl;
34 return 100;
35 }
分离的线程在同一个进程地址空间,相互的线程不会想回干扰,但是如果分离的线程崩溃,进程也会崩溃。
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 void* thread_run(void* arg)
8 {
9 pthread_detach(pthread_self());
10 while(1)
11 {
12 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
13 sleep(1);
14 break;
15 }
16 int a=10;
17 a=a/0;
18 return (void*)10;
19 }
20 int main()
21 {
22 pthread_t tid;
23 int ret=0;
24 ret= pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
25 if(ret!=0)
26 {
27 return -1;
28 }
29
30 sleep(10);
31 pthread_cancel(tid);
32 cout<<"new thread "<<tid<<" be cancled!"<<endl;
33 void* tmp=NULL;
34 pthread_join(tid,&tmp);
35 cout<<"thread qiut code:"<<(long long )ret<<endl;
36 return 100;
37 }
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 int a=10;
8 void* thread_run(void* arg)
9 {
10 while(1)
11 {
12 cout<<(char*)arg<<pthread_self()<<" pid:"<<getpid()<<endl;
13 cout<<(char*)arg<<" global-variable:"<<a<<" &a:"<<&a<<endl;
14 sleep(1);
15 }
16 return (void*)10;
17 }
18 int main()
19 {
20 pthread_t tid;
21 pthread_t tid1;
22 pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
23 pthread_create(&tid1,NULL,thread_run,(void*)"thread 1");
24 cout<<"main:"<<pthread_self()<<" pid:"<<getpid()<<endl;
25
26 cout<<"before:"<<main<<" global-variable:"<<a<<" %p:"<<&a<<endl;
27 sleep(5);
28 a=100;
29 cout<<"after:"<<main<<" globa-variable:"<<a<<" %p:"<<&a<<endl;
30
31 pthread_cancel(tid);
32 void* tmp=NULL;
33 pthread_join(tid,&tmp);
34 cout<<"thread qiut code:"<<(long long )tmp<<endl;
35 return 100;
36 }
多个线程并发同一段代码时,不会出现不同的结果。多个执行流,访问临界资源,不会导致程序产生二义性。
- 执行流:理解为线程
- 访问:指的是对临界资源进行操作
- 临界资源:指的是多个线程都可以访问到的资源
- eg:全局变量,某个结构体(不能是定义在某个线程入口函数内),某个类的实例化指针
- 临界区:代码操作临界资源的代码区域称之为临界区
- 二义性:结果会有多个
正常情况,假设我们定义一个变量 i 这个变量 i 一定是保存在内存的栈当中的,我们要对这个变量 i 进行计算的时候,是CPU(两大核心功能:算术运算和逻辑运算)来计算的,假设要对变量 i = 10 进行 +1 操作,首先要将内存栈中的 i 的值为 10 告知给寄存器,此时,寄存器中就有一个值 10,让后让CPU对寄存器中的这个 10 进行 +1 操作,CPU +1 操作完毕后,将结果 11 回写到寄存器当中,此时寄存器中的值被改为 11,然后将寄存器中的值回写到内存当中,此时 i 的值为 11。
假设有两个线程,线程A和线程B,线程A和线程B都想对全局变量 i 进行++。
假设全局变量 i 的值为 10,线程A从内存中把全局变量 i = 10 读到寄存器当中,此时,线程A的时间片到了,线程A被切换出来了,线程A的上下文信息中保存的是寄存器中的i = 10,程序计数器中保存的是下一条即将要执行的 ++ 指令,若此时线程B获取了CPU资源,也想对全局变量 i 进行 ++ 操作,因为此时线程A并未将运算结果返回到内存当中,所以线程B从内存当中读到的全局变量 i 的值还是10,然后将 i 的值读到寄存器中,然后再在CPU中进行 ++ 操作,然后将 ++ 后的结果 11,回写到寄存器,寄存器再回写到内存,此时内存当中 i 的值已经被线程B机型 ++ 后改为了 11,然后线程B将CPU资源让出来,此时线程A再切换回来的时候,它要执行的下一条指令是程序计数器中保存的对 i 进行 ++ 操作 ,而线程A此时 ++ 的 i 的值是从上下文信息中获取的,上下文信息中此时的 i = 10 ,此时线程A在CPU中完成对 i 的 ++ 操作,然后将结果 11 回写给寄存器,然后由寄存器再回写给内存,此时内存中的 i 被线程B改为了 11,虽然 ,线程A和线程B都对全局变量 i 进行了 ++ ,按理说最终全局变量 i 的值应该为12,而此时全局变量 i 的值却为11。
线程A对全局变量 i 加了一次,线程B也对全局变量 i 加了一次,而此时,全局变量的值为 11 而不是 12,由此就产生了多个线程同时操作临界资源的时候有可能产生二义性问题(线程不安全现象)
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5 int ticket=1000;
6
7 void* get_ticket(void* arg)
8 {
9 while(1)
10 {
11 if(ticket>0)
12 {
13 cout<<"i am "<<pthread_self()<<" get a ticket,no:"<<ticket<<endl;
14 ticket--;
15 }
16 else
17 {
18 break;
19 }
20 }
21 return NULL;
22 }
23 int main()
24 {
25 pthread_t tid[4];
26 for(int i=0;i<4;i++)
27 {
28 int ret=pthread_create(&tid[i],NULL,get_ticket,NULL);
29 if(ret!=0)
30 {
31 cout<<"线程创建失败!"<<endl;
32 }
33
34 }
35 for(int i=0;i<4;i++)
36 {
37 pthread_join(tid[i],NULL);
38 }
39
40 cout<<"pthread_join end!"<<endl;
41 return 0;
42 }
如上图所示,我们可以看到两个线程都拿到了第819张票,这就产生了二义性,即线程不安全现象。
- 线程在取票的时候,多个线程可能会拿到同一张票,,若CPU多的话有可能会拿到负数(互斥锁解决此问题)。
- 线程拿票不合理,可能一个线程A拿了所有的票,而另一个线程B只拿了一张票还与线程A相同。
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5 int ticket=100;
6
7 void* get_ticket(void* arg)
8 {
9 int* num=(int*)arg;
10 while(1)
11 {
12 if(ticket>0)
13 {
14 sleep(1);
15 cout<<"thread "<<num<<" get a ticket,no:"<<ticket<<endl;
16 ticket--;
17 }
18 else
19 {
20 break;
21 }
22 }
23 return NULL;
24 }
25 int main()
26 {
27 pthread_t tid[4];
28 for(int i=0;i<4;i++)
29 {
30 pthread_create(tid+1,NULL,get_ticket,(void*)i);
31
32 }
33 for(int i=0;i<4;i++)
34 {
35 pthread_join(tid[i],NULL);
36 }
37 return 0;
38 }
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
假设有一临界资源,有一个线程A和一个线程B,按之前的黄牛抢票的思路,只要线程拥有时间片就可以去访问这块临界资源,现在我们给线程 A 和线程 B 都加上互斥锁,假设此时线程A要去访问临界资源,它首先得获取互斥锁,而此时互斥锁中的值为1,表示当前可以访问,线程 A 去访问临界资源然后将互斥锁中的 1 改为 0 ,此时如果线程B如果想要访问临界资源之前先要获取互斥锁,而此时互斥锁中的值为0,所以线程 B 此时不能访问临界资源,等线程 A 访问完毕后,就会将锁释放,此时所中的值就会从 0 变为 1 , 此时线程 B 判断互斥锁中的值变为 1 可以访问了,就可以去访问临界资源了;互斥锁保证了当前临界资源在同一时刻只能被一个执行流访问。
注意:若要多个线程访问临界资源的时候是互斥访问的属性,一定要在多个线程中进行同一把锁的加锁操作,这样每个线程在访问临界资源之前都要获取这把锁,若锁中的值为 1 就能访问,为 0 则不能访问;若只给线程 A 加锁线程 B 不加锁,那么线程 A 判断锁中的值为 1 ,则访问临界资源并将锁中的值改为 0 ,而线程 B 为加这把锁,则不需要获取锁并判断锁中的值是否为 1 就可以直接对临界资源进行访问,会出现线程不安全现象。
加锁的时候会提前在寄存器的计数器中保存的一个值 0,而不管内存的计数器中保存的值为多少,都会将寄存器中保存到值 0 和内存计数器中保存的值进行交互,然后对寄存器中的值进行判断是否为 1 ,如果为 1 ,则能加锁,如果不为 1 ,则不能加锁。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
使用vim /usr/include/pthread.h路径下查看宏定义,这个宏中的PTHREAD_MUTEX_INITIALIZER 是初始化 pthread_mutex_t 这个变量的,如下图所示:
pthread_mutex_t 实际是一个联合体, vim /usr/include/bits/pthreadtypes.h 路径,静态初始化实际就是用上面那个宏初始化这个联合体,如下图所示
int pthread_mutex_init(pthread_mutex_t restrict mutex, const pthread_mutexattr_trestrict attr);
参数:
- mutex:要初始化的互斥量
- attr:NULL ,设置为空,代表默认属性
int pthread_mutex_destroy(pthread_mutex_t *mutex);动态初始化互斥锁变量的情况需要动态销毁互斥锁,否则就会造成内存泄漏
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 如果互斥锁变量当中的计数器的值为1,调用该接口,则加锁成功,该接口调用完毕,函数返回。
- 如果互斥锁变量当中的计数器的值为0,调用该接口,则调用该接口的执行流阻塞在当前接口内部。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 不管有没有加锁成功,都会返回,所以需要对加锁返回的结果进行判断,判断是否加锁,如果加锁成功,则操作临界资源。反之,则需要循环获取互斥锁,直到拿到互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 不管是阻塞加锁 / 非阻塞加锁 / 带有超 时时间的加锁,加锁成功的互斥锁,都可以使用该接口进行解锁。
在所有使用互斥锁的线程全部退出之后,就可以销毁互斥锁
线程访问临界资源之前,进行加锁操作
线程所有可能退出的地方进行解锁
在访问临界资源之前进行加锁,然后不解锁,会发生什么现象。
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5 int ticket=1000;
6
7 pthread_mutex_t g_lock; //全局变量的互斥锁
8
9 void* get_ticket(void* arg)
10 {
11 while(1)//1位置加锁还是在2位置加锁
12 {
13 pthread_mutex_lock(&g_lock);
14 //pos1
15 if(ticket>0)
16 {
17 cout<<"i am "<<pthread_self()<<" get a ticket,no:"<<ticket<<endl;
18 //pos2
19 ticket--;
20 }
21 else
22 {
23 break;
24 }
25 }
26 return NULL;
27 }
28 int main()
29 {
30 pthread_mutex_init(&g_lock,NULL);//初始化互斥锁
31 pthread_t tid[4];
32 for(int i=0;i<4;i++)
33 {
34 int ret=pthread_create(&tid[i],NULL,get_ticket,NULL);
35 if(ret!=0)
36 {
37 cout<<"线程创建失败!"<<endl;
38 }
39
40 }
41 for(int i=0;i<4;i++)
42 {
43 pthread_join(tid[i],NULL);
44 }
45
46 cout<<"pthread_join end!"<<endl;
47 pthread_mutex_destroy(&g_lock);
48 return 0;
49 }
观察到只得到一张票就不往下去执行了。
上图发现4个工作线程都被阻塞了,有一个工作线程加锁,之后未进行解锁,造成如上错误的原因是某个线程在执行完毕后,没有解锁,其他工作线程再次去获取锁时,互斥锁中计数器中的值还是0,就要被阻塞等待:所以加锁之后一定要记得解锁,否则就会导致死锁。
先mythread处于运行状态,在使用gdb attach [pid] 命令(用gdb调试一个正在运行的进程)进入gdb调试界面,如下图所示:
在用bt查看调用堆栈,在使用thread apply all bit 查看所有调用堆栈如下图:
然后通过 t [线程编号] 跳转到某一个线程的堆栈,然后通过 bt 查看线程的调用栈,f 跳转到某一个具体的堆栈里如上图所示, 我们可以进行调试
再打印互斥锁变量 My_lock,而在互斥锁变量中我们可以看到一个 __owner(互斥锁的拥有者) = 27700,而这个27700就是线程5,线程5此时再去加锁,但当它获取这把锁的时候,它就会阻塞在加锁逻辑中(即线程5第一次加锁成功了,第二次再去获取这把锁的时候就会阻塞在加锁逻辑中)
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5 int ticket=1000;
6
7 pthread_mutex_t g_lock; //全局变量的互斥锁
8
9 void* get_ticket(void* arg)
10 {
11 while(1)//1位置加锁还是在2位置加锁
12 {
13 pthread_mutex_lock(&g_lock);
14 //pos1
15 if(ticket>0)
16 {
17 usleep(1);
18 cout<<"i am "<<pthread_self()<<" get a ticket,no:"<<ticket<<endl;
19 //pos2
20 ticket--;
21 pthread_mutex_unlock(&g_lock);
22 }
23 else
24 {
25 pthread_mutex_unlock(&g_lock);
26 break;
27 }
28 }
29 return NULL;
30 }
31 int main()
32 {
33 pthread_mutex_init(&g_lock,NULL);//初始化互斥锁
34 pthread_t tid[4];
35 for(int i=0;i<4;i++)
36 {
37 int ret=pthread_create(&tid[i],NULL,get_ticket,NULL);
38 if(ret!=0)
39 {
40 cout<<"线程创建失败!"<<endl;
41 }
42
43 }
44 for(int i=0;i<4;i++)
45 {
46 pthread_join(tid[i],NULL);
47 }
48
49 cout<<"pthread_join end!"<<endl;
50 pthread_mutex_destroy(&g_lock);
51 return 0;
52 }
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
线程A获取到互斥锁1 ,线程B获取到互斥锁2的时候,线程A和线程B同时还想获取对方手里的锁(线程A还想获取互斥锁2,线程B还想获取互斥锁1),此时就会导致死锁
1 #include<iostream>
2 #include<unistd.h>
3 #include<pthread.h>
4 using namespace std;
5
6 pthread_mutex_t g_lock1;
7 pthread_mutex_t g_lock2;
8 void* ThreadStart1(void* args)
9 {
10 (void)args;
11 pthread_mutex_lock(&g_lock1);
12 sleep(5);
13 pthread_mutex_lock(&g_lock2);
14 return NULL;
15 }
16 void* ThreadStart2(void* args)
17 {
18 (void)args;
19 pthread_mutex_lock(&g_lock2);
20 sleep(5);
21 pthread_mutex_lock(&g_lock1);
22 return NULL;
23 }
24 int main()
25 {
26 pthread_mutex_init(&g_lock1,NULL);
27 pthread_mutex_init(&g_lock2,NULL);
28
29 pthread_t tid;
30 int ret=pthread_create(&tid,NULL,ThreadStart1,NULL);
31 if(ret<0)
32 {
33 cout<<"线程创建失败!"<<endl;
34 return 0;
35 }
36
37 ret=pthread_create(&tid,NULL,ThreadStart2,NULL);
38 if(ret<0)
39 {
40 cout<<"线程创建失败!"<<endl;
41 return 0;
42 }
43
44 while(1)
45 {
46 ;
47 }
48 pthread_mutex_destroy(&g_lock1);
49 pthread_mutex_destroy(&g_lock2);
50 return 0;
51 }
结果如下图:
通过gdb调试存在的进程如图:
查看所有线程的调用堆栈:
进入线程3并查看线程3的调用堆栈,进入其中一个调用堆栈。
线程ThreadStart1拥有g_lock1想拿g_lock2锁,线程ThreadStart2拥有g_lock2想拿g_lock1锁,这样两个线程就造成了死锁。
模拟吃面和做面的加锁例子:
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5
6 int g_bowl=1;
7 pthread_mutex_t g_lock;
8
9 void* EatStart(void* arg)
10 {
11 (void)arg;
12
13 while(1)
14 {
15 pthread_mutex_lock(&g_lock);
16 g_bowl--;
17 cout<<"i am:"<<pthread_self()<<" i eat:"<<g_bowl<<endl;
18 pthread_mutex_unlock(&g_lock);
19 }
20 return NULL;
21 }
22 void *MakeStart(void* arg)
23 {
24 (void)arg;
25 while(1)
26 {
27 pthread_mutex_lock(&g_lock);
28 g_bowl++;
29 cout<<"i am:"<<pthread_self()<<" i make:"<<g_bowl<<endl;
30 pthread_mutex_unlock(&g_lock);
31 }
32 return NULL;
33 }
34 int main()
35 {
36 pthread_mutex_init(&g_lock,NULL);
37
38 pthread_t tid_eat;
39 pthread_t tid_make;
40 int ret=pthread_create(&tid_eat,NULL,EatStart,NULL);
41 if(ret<0)
42 {
43 cout<<"线程创建失败!"<<endl;
44 }
45
46 ret=pthread_create(&tid_make,NULL,MakeStart,NULL);
47 {
48 if(ret<0)
49 {
50 cout<<"线程创建失败!\n"<<endl;
51 }
52 }
53
54 while(1)
55 {
56 sleep(1);
57 }
58
59
60 pthread_mutex_destroy(&g_lock);
61 return 0;
62 }
能会看到bowl已经减为负数,也有可能看到bowl加为超过1的正数,为什么会出现上述情况呢?因为线程EatStart负责吃面,线程MakeStart负责做面,当线程MakeStart拿到CPU资源时,它可能持续往碗里做面,就会出现bowl>1的情况,当线程EatStart拿到CPU资源时,它可能持续吃面,就会出现bowl<0的情况,然后一直吃面,导致bowl减少到0甚至负数的情况。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态初始化:
int pthread_cond_init(pthread_cond_t restrict cond, const pthread_condattr_t restrict attr);
- cond:待要初始化的“条件变量”的变量,一般情况下,传递一个pthread_cond_t类型变量的地址
- attr:一般情况下给NULL,采用默认属性
int pthread_cond_destroy(pthread_cond_t cond)
int pthread_cond_signal(pthread_cond_t cond);
- 作用:通知PCB等待队列当中的线程,线程接收到了,则从PCB等待队列当中出队操作。
- 至少唤醒一个PCB等待队列当中的线程。
int pthread_cond_broadcast(pthread_cond_t cond);
- 唤醒所有PCB等待队列当中的线程。
int pthread_cond_wait(pthread_cond_t restrict cond,pthread_mutex_t restrict mutex);
参数:
- cond:要在这个条件变量上等待
- mutex:互斥量,后面详细解释
给线程EatStart加上条件变量bowl小于等于0就不可以在吃面,给线程MakeStart加上条件变量bowl>1就不可以在做面。
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5
6 int g_bowl=1;
7 pthread_mutex_t g_lock;
8
9 pthread_cond_t g_eat_cond;
10 pthread_cond_t g_make_cond;
11
12 //吃面
13 void* EatStart(void* arg)
14 {
15 (void)arg;
16
17 while(1)
18 {
19 pthread_mutex_lock(&g_lock);
20 if(g_bowl<=0)
21 {
22 pthread_cond_wait(&g_eat_cond,&g_lock);//传递互斥锁是后面要解锁
23 }
24 g_bowl--;
25 cout<<"i am:"<<pthread_self()<<" i eat:"<<g_bowl<<endl;
26 pthread_mutex_unlock(&g_lock);
27
28 pthread_cond_signal(&g_make_cond);//通知做面的人来做面
29 }
30 return NULL;
31 }
32
33 //做面
34 void *MakeStart(void* arg)
35 {
36 (void)arg;
37 while(1)
38 {
39 pthread_mutex_lock(&g_lock);
40 if(g_bowl>0)
41 {
42 pthread_cond_wait(&g_make_cond,&g_lock);
43 }
44 g_bowl++;
45
46 cout<<"i am:"<<pthread_self()<<" i make:"<<g_bowl<<endl;
47 pthread_mutex_unlock(&g_lock);
48
49 pthread_cond_signal(&g_eat_cond);//通知吃面的人来吃面
50 }
51 return NULL;
52 }
53 int main()
54 {
55 //初始化互斥锁
56 pthread_mutex_init(&g_lock,NULL);
57
58 //初始化条件变量
59 pthread_cond_init(&g_eat_cond,NULL);
60 pthread_cond_init(&g_make_cond,NULL);
61
62 int i=0;
63 for(i=0;i<1;i++)
64 {
65 pthread_t tid;
66 int ret=pthread_create(&tid,NULL,EatStart,NULL);
67 if(ret<0)
68 {
69 cout<<"线程创建失败!"<<endl;
70 }
71
72 ret=pthread_create(&tid,NULL,MakeStart,NULL);
73 {
74 if(ret<0)
75 {
76 cout<<"线程创建失败!\n"<<endl;
77 }
78 }
79 }
80
81 while(1)
82 {
83 sleep(1);
84 }
85
86 //销毁互斥锁
87 pthread_mutex_destroy(&g_lock);
88
89 //销毁条件变量
90 pthread_cond_destroy(&g_eat_cond);
91 pthread_cond_destroy(&g_make_cond);
92 return 0;
93 }
1 #include<iostream>
2 #include<pthread.h>
3 #include<unistd.h>
4 using namespace std;
5
6 int g_bowl=1;
7 pthread_mutex_t g_lock;
8
9 pthread_cond_t g_eat_cond;
10 pthread_cond_t g_make_cond;
11
12 //吃面
13 void* EatStart(void* arg)
14 {
15 (void)arg;
16
17 while(1)
18 {
19 pthread_mutex_lock(&g_lock);
20 if(g_bowl<=0)
21 {
22 pthread_cond_wait(&g_eat_cond,&g_lock);//传递互斥锁是后面要解锁
23 }
24 g_bowl--;
25 cout<<"i am:"<<pthread_self()<<" i eat:"<<g_bowl<<endl;
26 pthread_mutex_unlock(&g_lock);
27
28 pthread_cond_signal(&g_make_cond);//通知做面的人来做面
29 }
30 return NULL;
31 }
32
33 //做面
34 void *MakeStart(void* arg)
35 {
36 (void)arg;
37 while(1)
38 {
39 pthread_mutex_lock(&g_lock);
40 if(g_bowl>0)
41 {
42 pthread_cond_wait(&g_make_cond,&g_lock);
43 }
44 g_bowl++;
45
46 cout<<"i am:"<<pthread_self()<<" i make:"<<g_bowl<<endl;
47 pthread_mutex_unlock(&g_lock);
48
49 pthread_cond_signal(&g_eat_cond);//通知吃面的人来吃面
50 }
51 return NULL;
52 }
53 int main()
54 {
55 //初始化互斥锁
56 pthread_mutex_init(&g_lock,NULL);
57
58 //初始化条件变量
59 pthread_cond_init(&g_eat_cond,NULL);
60 pthread_cond_init(&g_make_cond,NULL);
61
62 int i=0;
63 for(i=0;i<2;i++)
64 {
65 pthread_t tid;
66 int ret=pthread_create(&tid,NULL,EatStart,NULL);
67 if(ret<0)
68 {
69 cout<<"线程创建失败!"<<endl;
70 }
71
72 ret=pthread_create(&tid,NULL,MakeStart,NULL);
73 {
74 if(ret<0)
75 {
76 cout<<"线程创建失败!\n"<<endl;
77 }
78 }
79 }
80
81 while(1)
82 {
83 sleep(1);
84 }
85
86 //销毁互斥锁
87 pthread_mutex_destroy(&g_lock);
88
89 //销毁条件变量
90 pthread_cond_destroy(&g_eat_cond);
91 pthread_cond_destroy(&g_make_cond);
92 return 0;
93 }
假设,碗里有面,此时make1拿到了锁,则make1判断碗里有面后将自己放入PCB等待队列中进行等待,然后释放互斥锁,假设此时,eat1拿到了互斥锁,然后eat1吃掉碗里的面,然后释放锁并通知PCB等待队列,此时make1已经出队,假设此时make2拿到了锁,并做了一碗面,然后释放锁,然后make1又拿到了锁,而此时make1将要执行的是跳过pthread_cond_wait函数,则make1跳过了判断碗里是否有面,直接往碗里做面,此时bowl的值就由1变为了2。
假设,碗里有面,此时eat1拿到了锁,则eate1判断碗里没有面后将自己放入PCB等待队列中进行等待,然后释放互斥锁,假设此时,make1拿到了互斥锁,然后make1做碗里的面,然后释放锁并通知PCB等待队列,此时eat1已经出队,假设此时eat2拿到了锁,并吃了一碗面,然后释放锁,然后eat1又拿到了锁,而此时eat1将要执行的是跳过了pthread_cond_wait函数,则eate1跳过了判断碗里是否有面,直接往碗里吃面,此时bowl的值就由0变成-1。
解决这个问题只需要把if判断条件改成while循环即可。
#include
#include
#include
using namespace std;
int g_bowl=1;
pthread_mutex_t g_lock;
pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;
//吃面
void* EatStart(void* arg)
{
(void)arg;
while(1)
{
pthread_mutex_lock(&g_lock);
while(g_bowl<=0)
{
pthread_cond_wait(&g_eat_cond,&g_lock);//传递互斥锁是后面要解锁
}
g_bowl--;
cout<<"i am:"<<pthread_self()<<" i eat:"<<g_bowl<<endl;
pthread_mutex_unlock(&g_lock);
pthread_cond_signal(&g_make_cond);//通知做面的人来做面
}
return NULL;
}
//做面
void *MakeStart(void* arg)
{
(void)arg;
while(1)
{
pthread_mutex_lock(&g_lock);
while(g_bowl>0)
{
pthread_cond_wait(&g_make_cond,&g_lock);
}
g_bowl++;
cout<<"i am:"<<pthread_self()<<" i make:"<<g_bowl<<endl;
pthread_mutex_unlock(&g_lock);
pthread_cond_signal(&g_eat_cond);//通知吃面的人来吃面
}
return NULL;
}
int main()
{
//初始化互斥锁
pthread_mutex_init(&g_lock,NULL);
//初始化条件变量
pthread_cond_init(&g_eat_cond,NULL);
pthread_cond_init(&g_make_cond,NULL);
int i=0;
for(i=0;i<2;i++)
{
pthread_t tid;
int ret=pthread_create(&tid,NULL,EatStart,NULL);
if(ret<0)
{
cout<<"线程创建失败!"<<endl;
}
ret=pthread_create(&tid,NULL,MakeStart,NULL);
{
if(ret<0)
{
cout<<"线程创建失败!\n"<<endl;
}
}
}
while(1)
{
sleep(1);
}
//销毁互斥锁
pthread_mutex_destroy(&g_lock);
//销毁条件变量
pthread_cond_destroy(&g_eat_cond);
pthread_cond_destroy(&g_make_cond);
return 0;
}
由于需要是在pthread_cond_wait函数内部进行解锁,当有线程进去之后要把锁释放别人才能用,解锁之后,其他的执行流才能获取到这把互斥锁,所以,需要传入互斥锁,否则,如果在调用pthread_cond_wait线程在进行等待的时候,不释放互斥锁,其他线程就不能解锁。
假设,吃面的线程判断碗里没有面,要进行等待,若它先释放互斥锁,在它释放完互斥锁还未进入等待队列之前,可能做面的线程拿到了互斥锁,并做完了面,通知PCB等待队列,而此时吃面的线程,还未进入PCB等待队列,PCB等待队列此事为空,做面的线程又拿到互斥锁去做面,此时,做面的线程判断碗里有面,则将自己放入PCB等待队列中进行等待,而此时吃面的线程也在PCB等待队列中进行等待,所以不能先释放互斥锁。
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
BlockingQueue 在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。
BlockQueue.cpp
1 #pragma once //防止头文件重复包含
2
3 #include<iostream>
4
5 #include<pthread.h>
6 #include<unistd.h>
7 #include<queue>
8 //using namespace std;
9
10 class Task
11 {
12 public:
13 int _x;
14 int _y;
15 public:
16 Task()
17 {}
18 Task(int x,int y)
19 :_x(x)
20 ,_y(y)
21 {}
22 int run()
23 {
24 return _x+_y;
25 }
26 ~Task()
27 {}
28 };
29 class BlockQueue
30 {
31 private:
32 // std::queue q; //设置一个队列
33 std::queue<Task> q; //设置一个队列
34 int _cap; //容量
35 pthread_mutex_t lock; //设置一把互斥锁
36
37 pthread_cond_t c_cond; //满了的话通知消费者
38 pthread_cond_t p_cond; //空的话通知生产者
39
40 private: //封装起来
41 void LockQueue() //加锁
42 {
43 pthread_mutex_lock(&lock);
44 }
45 void UnLockQueue() //解锁
46 {
47 pthread_mutex_unlock(&lock);
48 }
49
50
51 bool IsEmpty() //判断队列是否为空
52 {
53 return q.size()==0;
54 }
55 bool IsFull() //判断队列是否满了
56 {
57 return q.size()==_cap;
58 }
59
60 void ProductWait() //生产者等待
61 {
62 pthread_cond_wait(&p_cond,&lock);
63 }
64 void ConsumerWait() //消费者等待
65 {
66 pthread_cond_wait(&c_cond,&lock);
67 }
68
69 void WakeUpProduct() //唤醒生产者
70 {
71 std::cout<<"wake up Product..."<<std::endl;
72 pthread_cond_signal(&p_cond);
73 }
74 void WakeUpConsumer() //唤醒消费者
75 {
76 std::cout<<"wake up Consumer..."<<std::endl;
77 pthread_cond_signal(&c_cond);
78 }
79
80 public:
81 BlockQueue(int cap) //构造函数初始化
82 :_cap(cap)
83 {
84 pthread_mutex_init(&lock,NULL);
85 pthread_cond_init(&c_cond,NULL);
86 pthread_cond_init(&p_cond,NULL);
87 }
88 ~BlockQueue() //析构函数销毁
89 {
90 pthread_mutex_destroy(&lock);
91 pthread_cond_destroy(&c_cond);
92 pthread_cond_destroy(&p_cond);
93 }
94
95 void put(Task in)
96 {
97 //Queue是临界资源,就要加锁,而且判断是否为满,把接口封装起来
98 LockQueue();
99 while(IsFull())
100 {
101 WakeUpConsumer();
102 std::cout<<"queue full,notify consume data,product stop!"<<std::endl;
103 ProductWait(); //生产者线程等待
104 }
105 q.push(in);
106
107 UnLockQueue();
108 }
109 void Get(Task& out)
110 {
111 LockQueue();
112 while(IsEmpty())
113 {
114 WakeUpProduct();
115 std::cout<<"queue empty,notify product data,consumer stop"<<std::endl;
116 ConsumerWait();
117 }
118 out=q.front();
119 q.pop();
120
121 UnLockQueue();
122 }
123
124 //线程接口函数
125 /*void* Product(void* arg)
126 {
127
128 }
129 void* Consumer(void* arg)
130 {
131
132 }*/
133
134 };
main.cpp
1 #include"BlockQueue.cpp"
2 using namespace std;
3 #include<stdlib.h>
4
5 pthread_mutex_t p_lock;
6 pthread_mutex_t c_lock;
7 void* Product_Run(void* arg)
8 {
9 BlockQueue* bq=(BlockQueue*)arg;
10
11 srand((unsigned int)time(NULL));
12 while(true)
13 {
14 pthread_mutex_lock(&p_lock);
15 // int data=rand()%10+1;
16 int x=rand()%10+1;
17 int y=rand()%100+1;
18 Task t(x,y);
19 bq->put(t);
20 pthread_mutex_unlock(&p_lock);
21 cout<<"product data is:"<<t.run()<<endl;
22 }
23 }
24 void* Consumer_Run(void* arg)
25 {
26 BlockQueue* bq=(BlockQueue*)arg;
27 while(true)
28 {
29 pthread_mutex_lock(&c_lock);
30 // int n=0;
31 Task t;
32 bq->Get(t);
33 pthread_mutex_unlock(&c_lock);
34 cout<<"consumer is:"<<t._x<<"+"<<t._y<<"="<<t.run()<<endl;
35 sleep(1);
36 }
37 }
38 int main()
39 {
40 BlockQueue* bq=new BlockQueue(10);
41 pthread_t c,p;
42
43 pthread_create(&c,NULL,Product_Run,(void*)bq);
44
45 pthread_create(&p,NULL,Consumer_Run,(void*)bq);
46
47 pthread_join(c,NULL);
48 pthread_join(p,NULL);
49
50 delete bq;
51 return 0;
52 }
makefile
1 main:main.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f main
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
#include
int sem_init(sem_t sem, int pshared, unsigned int value);参数:
- pshared:0表示线程间共享,非零表示进程间共享
- value:信号量初始值
int sem_destroy(sem_t sem);
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t sem);
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t sem);
RingQueue.cpp
1 #pragma once
2
3 #include<iostream>
4 #include<unistd.h>
5 #include<vector>
6 #include<semaphore.h>
7
8 #include<stdlib.h>
9 #define NUM 10
10
11 class RingQueue
12 {
13 private:
14 std::vector<int> v;
15 int _cap; //容量
16 sem_t sem_blank; //生产者
17 sem_t sem_data; //消费者
18
19 int c_index; //消费者索引
20 int p_index; //生产者索引
21
22 public:
23 RingQueue(int cap=NUM)
24 :_cap(cap)
25 ,v(cap)
26 {
27 sem_init(&sem_blank,0,cap);
28 sem_init(&sem_data,0,0);
29 c_index=0;
30 p_index=0;
31 }
32 ~RingQueue()
33 {
34 sem_destroy(&sem_blank);
35 sem_destroy(&sem_data);
36 }
37
38 void Get(int& out)
39 {
40 sem_wait(&sem_data);
41 //消费
42 out=v[c_index];
43 c_index++;
44 c_index=c_index%NUM; //防止越界,构成环形队列
45 sem_post(&sem_blank);
46 }
47 void Put(const int& in)
48 {
49 sem_wait(&sem_blank);
50 //生产
51 v[p_index]=in;
52 p_index++;
53 p_index=p_index%NUM;
54 sem_post(&sem_data);
55 }
56 };
main.cpp
1 #include"RingQueue.h"
2 using namespace std;
3
4
5 void* Consumer(void* arg)
6 {
7 RingQueue *bq=(RingQueue*)arg;
8 int data;
9 while(1)
10 {
11 bq->Get(data);
12 cout<<"i am:"<<pthread_self()<<" i consumer:"<<data<<endl;
13 }
14 }
15 void* Product(void* arg)
16 {
17 RingQueue* bq=(RingQueue*)arg;
18 srand((unsigned int)time(NULL));
19 while(1)
20 {
21 int data=rand()%100;
22 bq->Put(data);
23 cout<<"i am:"<<pthread_self()<<" i product:"<<data<<endl;
24 sleep(1);
25 }
26 }
27 int main()
28 {
29 RingQueue* pq=new RingQueue();
30 pthread_t c;
31 pthread_t p;
32 pthread_create(&c,NULL,Consumer,(void*)pq);
33 pthread_create(&p,NULL,Product,(void*)pq);
34
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
什么是线程池?简单点说,线程池就是有一堆已经创建好了的线程,初始它们都处于空闲等待状态,当有新的任务需要处理的时候,就从这个池子里面取一个空闲等待的线程来处理该任务,当处理完成了就再次把该线程放回池中,以供后面的任务使用。当池子里的线程全都处理忙碌状态时,线程池中没有可用的空闲等待线程,此时,根据需要选择创建一个新的线程并置入池中,或者通知任务线程池忙,稍后再试。
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
Thread_Pool.h:
1 #include<iostream>
2 #include<math.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 #include<pthread.h>
6 #include<queue>
7
8 #define NUM 5
9 class Task
10 {
11 private:
12 int _b;
13 public:
14 Task()
15 {
16
17 }
18 Task(int b)
19 :_b(b)
20 {
21
22 }
23 ~Task()
24 {
25
26 }
27 void Run()
28 {
29 std::cout<<"i am:"<<pthread_self()<<" Task run.... :base# "<<_b<<" pow is "<<pow(_b,2)<<std::endl;
30 }
31 };
32 class ThreadPool
33 {
34 private:
35 std::queue<Task*> q;
36 int _max_num; //线程总数
37
38 pthread_mutex_t lock;
39 pthread_cond_t cond; //只能让消费者操作
40
41 private:
42 void LockQueue()
43 {
44 pthread_mutex_lock(&lock);
45 }
46 void UnLockQueue()
47 {
48 pthread_mutex_unlock(&lock);
49 }
50
51 bool IsEmpty()
52 {
53 return q.size()==0;
54 }
55 bool IsFull()
56 {
57 return q.size()==_max_num;
58 }
59
60 void ThreadWait()
61 {
62 pthread_cond_wait(&cond,&lock); //等待条件变量满足
63 }
64
65 void ThreadWakeUp()
66 {
67 pthread_cond_signal(&cond);
68 }
69 public:
70 ThreadPool(int max_num=NUM )
71 :_max_num(max_num)
72 {
73
74 }
75
76 static void* Routine(void* arg)
77 {
78 while(1)
79 {
80 ThreadPool *tp=(ThreadPool*)arg;
81 while(tp->IsEmpty())
82 {
83 tp->LockQueue(); //静态成员方法不能访问非静态成员方法,所以传(void*)this传过去
84 tp->ThreadWait(); //为空挂起等待
85 }
86
87 Task t;
88 tp->Get(t); //获取这个任务
89 tp->UnLockQueue();
90 t.Run(); //拿到这个任务运行
91 }
92 }
93
94 void ThreadPoolInit()
95 {
96 pthread_mutex_init(&lock,NULL);
97 pthread_cond_init(&cond,NULL);
98
99 int i=0;
100 pthread_t t;
101 for(i=0;i<_max_num;i++)
102 {
103 pthread_create(&t,NULL,Routine,(void*)this);
104 }
105 }
106 ~ThreadPool()
107 {
108 pthread_mutex_destroy(&lock);
109 pthread_cond_destroy(&cond);
110 }
111
112 //server 放数据
113 void Put(Task& in)
114 {
115 LockQueue();
116
117 q.push(&in);
118
119 UnLockQueue();
120
121 ThreadWakeUp();
122 }
123 //ThreadPool 取数据
124 void Get(Task& out)
125 {
126 //线程池里面直接拿不用加锁
127 Task* t=q.front();
128 q.pop();
129 out=*t;
130 }
131 };
132
main.cpp
1 #include"Thread_Pool.h"
2 using namespace std;
3
4
5 int main()
6 {
7 ThreadPool *tp=new ThreadPool();
8
9 tp->ThreadPoolInit();
10
11 while(true)
12 {
13 int x=rand()%10+1;
14 Task t(x);
15 tp->Put(t);
16 sleep(1);
17 }
18 return 0;
19 }
makefile
1 main:main.cpp
2 g++ $^ -o $@ -lpthread
3 .PHONY:clean
4 clean:
5 rm -f main
单例模式是一种创建型模式,它会限制应用程序,使其只能创建某一特定类>类型的一个单一的实例。举例来说,一个web站点将会需要一个数据库连接>对象,但是应该有且只能有一个,因此我们通过使用单例模式来实现这种限>制。我们可以使用一个静态属性来保证对于一个特定的类来说只存在一个单一的>实例。
某些类, 只应该具有一个对象(实例), 就称之为单例。
template <typename T>
class Singleton
{
private:
static T data; //定义静态的类对象,程序加载类就加载对象
public:
static T* GetInstance()
{
return &data;
}
};
class Singleton
{
static T* inst; //定义静态的类对象指针,程序运行时才加载对象
public:
static T* GetInstance()
{
if (inst == NULL)
{
inst = new T();
} r
eturn inst;
}
};
存在一个严重的问题, 线程不安全.第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.但是后续再次调用, 就没有问题了。
template <typename T>
// 懒汉模式, 线程安全
template <typename T>
class Singleton {
volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
static std::mutex lock;
public:
static T* GetInstance()
{
if (inst == NULL) // 双重判定空指针, 降低锁冲突的概率, 提高性能. //判断两个线程不同时进去直接return
{
lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new. //两个线程同时进去加锁
if (inst == NULL)
{
inst = new T();
}
lock.unlock();
}
return inst;
}
};
不是.原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全。
以上就是今天要讲的内容,本文超级详细介绍了Linux多线程概念和线程控制、死锁、吃面的使用、线程池的实现、生产者、消费者等的使用,多线程提供了大量的方法供我们使用,非常的便捷,我们务必掌握。希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。加油啊!