一、线程的概念
我们知道进程有自己独立的地址空间运行,在一个程序里的一个执行路线就叫做线程(thread)。
更准确的定义是:线程是“一个进程内部的控制序列“,一切进程至少都有一个执行线程。
进程是资源竞争的基本单位,线程是程序执行的最小单位。
1、进程的多个线程共享:
1)同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境
2)文件描述符表
3)每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
4)当前工作目录
5)用户id和组id
2、线程有些资源是每个线程各有一份的
1)线程id
2)上下文,包括各种寄存器的值,程序计数器和栈指针
3)栈空间
4)调度优先级
1)线程id
2)上下文,包括各种寄存器的值,程序计数器和栈指针
3)栈空间
4)调度优先级
二、线程的优缺点
1、优点
1)创建一个新线程的代价要比创建一个新进程小得多
2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3)线程占用的资源要比进程少很多
4)能充分利用多处理器的可并行数量
5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
6)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
7)I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
2、缺点
1)性能损失
2)健壮性降低
3)缺乏访问控制
4)编程难度提高
三、线程的创建
1、创建线程要用pthread_creat函数
![](https://i-blog.csdnimg.cn/blog_migrate/8d55b5713e843adc9287c79325d84045.png)
参数:
attr表示线程属性,线程调用pthread_ctreate时创建新线程,当前线程pthread_create()将继续往下执行,新线程的代码由start_routine()决定,start_routine()有一个参数,就是arg。
返回值:
成功返回0,失败返回值错误号,以前我们知道系统函数是成功返回0,失败返回-1,而错误号保存在全局变量errno中;
而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但是这是为了兼容其他函数接口而提供的,pthread库本身并不使用它,通过返回值,返回错误码更加清晰
错误信息:
由于pthread_create的错误码不保存在errno中,因此不能直接⽤用perror(3)打印错误信息,可以先用strerror(3)把错误码转换成错误信息再打印。
start_routine()的返回值类型也是void*。
代码实现线程的创建:
1 #include<unistd.h>
2 #include<stdlib.h>
3 #include<stdio.h>
4 #include<pthread.h>
5 #include<sys/types.h>
6
7 void *thread_run(void *arg){
8 printf("I am new thread :pid:%d,thread:%u\n",getpid(),pthread_self());
9 sleep(5);
10 return (void*)3;
11 }
12
13 int main(){
14 pthread_t tid;
15 int ret;
16 if((ret=pthread_create(&tid,NULL,thread_run,NULL))!=0){
17 fprintf(stderr,"pthread_create:%s\n",strerror(ret));
18 exit(EXIT_FAILURE);
19 }
20 printf("i am main thread:pid:%d,thread:%u\n",getpid(),pthread_self());
21 pthread_join(tid,NULL);
22 return 0;
23 }
在这里注意,pthread.h不是Linux默认的库,因此在链接时需要使用静态库libthread.a,所以在编译时要加-lpthread参数,如下:
可以看到两个线程属于同一个进程。
四、线程的终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2. 线程可以调用pthread_ exit终止自己。
3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
看一下pthread_exit函数----线程终止
参数:
retval是void*类型,和线程函数返回值用法一样,其他线程调用pthread_join获得这个指针
注:pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是malloc分配的,不能从线程函数的栈上分配。因为当其它线程得到这个返回值指针时,线程函数已经退出。
注:pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是malloc分配的,不能从线程函数的栈上分配。因为当其它线程得到这个返回值指针时,线程函数已经退出。
了解pthread_cancel函数----线程取消
![](https://i-blog.csdnimg.cn/blog_migrate/4003998d4fe2cf54091a774e8b43d958.png)
参数:
thread:线程ID
返回值:成功返回0;失败返回错误码
五、线程等待
当已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,或者创建新的线程不会复用刚才退出线程的地址空间时,就需要线程等待。
pthread_join函数---等待线程结束
![](https://i-blog.csdnimg.cn/blog_migrate/6a4042e60b3c0074afd0101c0c877b27.png)
参数:
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
PS:调用该函数的线程将挂起等待,直到id为thread的线程终止。
thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2.如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
3.如果thread线程是自己调用pthread_exit终止的,valueptr所指向的单元存放的是传给pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
使用函数举例:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<pthread.h>
6
7 void *thread1(void *arg){
8 printf("thread 1 returning...\n");
9 int *p=(int*)malloc(sizeof(int));
10 *p=1;
11 return (void*)p;
12 }
13
14 void *thread2(void *arg){
15 printf("thread 2 exiting...\n");
16 int *p=(int*)malloc(sizeof(int));
17 *p=2;
18 pthread_exit((void*)p);
19 }
20
21 void *thread3(void *arg){
22 while(1){
23 printf("thread3 is running..\n");
24 sleep(1);
25 }
26 return NULL;
27 }
28
29 int main(){
30 pthread_t tid;
31 void *ret;
32
33 //thread1 return
34 pthread_create(&tid,NULL,thread1,NULL);
35 pthread_join(tid,&ret);
36 printf("thread return,thread id %X,return code:%\n",tid,*(int*)ret);
37 free(ret);
38
39 //thread2 exit
40 pthread_create(&tid,NULL,thread2,NULL);
41 pthread_join(tid,&ret);
42 printf("thread rerurn,thread id %X,return code:%d\n",tid,*(int*)ret);
43 free(ret);
44
45 //thread 3 cancel by other
46 pthread_create(&tid,NULL,thread3,NULL);
47 sleep(3);
48 pthread_cancel(tid);
49 pthread_join(tid,&ret);
50 if(ret==PTHREAD_CANCELED)
51 printf("thread return,thread id %X,return code:PTHREAD_CANCELED\n",tid);
52 else
53 printf("thread return,thread id %X,return code:NULL\n",tid);
54 }
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/e068268b6b6c3e7a28ea63d1ea3b4373.png)
六、线程的合并与分离
线程是可结合(joinable)的或者是可分离的(detached).一个线程可以被其他线程收回资源和杀死,在被其他线程回收之前,它的存储器资源是不释放,相反,一个分离的线程是不能被其他线程回收或者杀死,它的存储器资源是由系统自动释放。
1、默认情况下,线程被创建是可结合的,线程退出后,需要对其进⾏行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
2、如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
将子线程状态设置为分离(detached),该线程运行结束会自动释放所有资源。
2、如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
将子线程状态设置为分离(detached),该线程运行结束会自动释放所有资源。
注意:joinable和分离是冲突的,一个线程不能既是joinable又是分离的;
分离线程可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
分离线程可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
函数应用如下:
![](https://i-blog.csdnimg.cn/blog_migrate/bc4ad16310009ec0aebe35c28a842f0c.png)
函数应用举例:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<pthread.h>
6
7 void *thread_run(void *arg){
8 pthread_detach(pthread_self());
9 printf("%s\n",(char*)arg);
10 return NULL;
11 }
12
13 int main(){
14 pthread_t tid;
15 if(pthread_create(&tid,NULL,thread_run,"thread1 run...")!=0){
16 printf("create thread error\n");
17 return 1;
18 }
19 int ret=0;
20 sleep(1);
21 if(pthread_join(tid,NULL)==0){
22 printf("pthread wait success\n");
23 ret=0;
24 }
25 else{
26 printf("pthread wait failed\n");
27 ret=1;
28 }
29 return ret;
30 }
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/f4ec00ef1482286952e90950b151dad8.png)