一 .线程的概念
1. 线程的定义
线程是一个进程内部的控制序列,所有线程共享进程虚拟地址空间;一切进程至少有一个线程。
当一个进程只有一个线程时,即进程本身就是线程,也就是主线程。
线程不包含大部分的资源,只拥有少量的资源,节省资源,但是一个进程包含多个线程,线程的数量比较大,管理比较麻烦。
在window下,管理线程的数据结构是TCB;在Linux下,线程是轻量级进程,在CPU眼中,只有一个个轻量级PCB。
2. 多个线程共享的内容
由于多个线程共享一块进程地址空间,即共享地址空间的Text Segment,Data Segment。
(1)如果定义一个函数,在各线程中都可以调用;如果定义一个全局变量,在各线程中都可以访问。
(2)文件描述符
(3)每种信号的处理方式
(4)当前工作目录
(5)用户id和组id
3. 线程的私有内容
(1)线程有独立的线程号,用LWP表示;在操作系统内核中和用户态的id值是不同的;
(2)线程有自己独立的栈空间,即虽然函数代码是共享的,但是不同的线程在不同的栈空间进行函数压栈。
(3)线程有自己独立的上下文数据,即线程执行过程的静态描述;
4. 线程和进程的不同
(1)Linux下进程是资源分配的最小单位;
(2)Linux下线程是调度的最小单位;
(3)Linux下线程是程序执行的最小单位;
(4)线程共享进程的地址空间;
5. 线程的优点
(1)创建一个新线程的代价要比创建一个新进程小的多;
(2)线程之间的切换要比进程之间的切换小的多;
(3)线程占用的资源要比进程小的多;
(4)能充分利用多处理器的可并行数量;
(5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;
(6)计算密集型应用,为了能在多处理器系统上运行,将计算分配到多个线程中实现;
(7)在I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以等待不同的I/O操作;
6. 线程的缺点
(1)性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器,如果计算密集型线程比可用的处理器多,那么可能会有较大的性能损失,即增加了额外的同步和调度开销,而可用的资源不变。
(2)健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序中,可能会因为共享了不该共享的变量而造成不良影响,换句话说,线程之间是缺乏保护的。
(3)缺乏访问控制
进程是访问控制的基本粒度,在一个进程中调用某些OS函数会对整个进程造成影响。
(4)编程难度提高
编写和调试一个多线程程序比单线程程序难的多。
二 . 线程控制
POSIX线程库(用户库)
与线程有关的函数构成了一个完整的系列,大部分的函数是pthread 开头的;
要使用这些函数库,必须引入头文件<pthread.h>;
链接这些线程函数时要使用编译命令的“-lpthread”选项;
1. 创建线程
函数原型:
功能:创建一个新的线程
参数:thread 指向进程的地址空间,返回线程id;attr 表示线程的属性,默认为NULL;第三个参数表示函数指针,指向线程要执行的函数入口;arg 表示传给线程启动函数的参数。
返回值:成功返回0,失败返回错误码。
创建线程代码:
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <pthread.h>
6
7 void* rout(void* arg)
8 {
9 int i;
10 for(;;)
11 {
12 printf("I am new thread\n");
13 sleep(1);
14
15 }
16 }
17 int main()
18 {
19 pthread_t tid;
20 int ret;
21 ret=pthread_create(&tid,NULL,rout,NULL);
22 if(ret!=0)
23 {
24 printf("error");
25 exit(EXIT_FAILURE);
26 }
27
28 int i=0;
29 for(;;)
30 {
31 printf("I am main thread\n");
32 sleep(1);
33 }
34 }
35
注:在编译链接时需要加上 -lpthread 。
2. 线程终止
如果想要终止某个线程而不终止进程,可以有三个方法:
(1)从线程函数return,但这种方法对主线程不适用;
(2)调用pthread_exit 来终止自己;
函数原型:
功能:终止线程
参数:retval 表示要终止的线程的地址空间,不能返回一个局部变量;
返回值:成功返回0,失败返回错误码。
注:pthread_exit或者return返回的指针不能是线程函数的局部变量,必须是全局变量或者堆上的变量,因为当其他线程得到这个返回值时线程函数已经退出了。
(3)调用pthread_cancel 来终止另一个线程;
函数原型:
参数:要终止的线程id。
返回值:成功返回0,失败返回错无码。
3. 线程等待
(1)为什么有线程等待
进程等待是为了获得子进程的退出信息和退出码,然后回收子进程,防止了资源泄漏;而线程等待与进程等待相类似,也是为了回收新线程,防止资源泄露。
在线程等待中,我们不需要考虑线程执行结果是否正确,即不用考虑线程异常退出的情况。这是因为一个线程的异常退出,会导致整个进程的退出,进程退出之后自然有父进程回收了,就不用再管线程了。
(2)线程等待函数pthread_join
函数原型:
功能:等待线程并且回收线程
参数:thread 表示要等待线程的id,retval 是一个二级指针,指向线程的返回值。
返回值:成功返回0.失败返回错误码;
注:调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的.
(1)return 返回,retval 指向存放thread线程函数的返回值的单元;
(2)调用pthread_exit返回,retval 指向存放传给pthread_exit的参数的单元;
(3)调用pthread_cancel返回,retval 指向的单元存放的是常数PTHREAD_CANCELED;
(4) 若对线程的终止状态不感兴趣,可以传NULL给retval。
线程终止及等待代码:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <pthread.h>
5 #include <stdlib.h>
6
7 void *thread1(void* arg)
8 {
9 printf("thread1 returning...\n");
10 int *p=(int*)malloc(sizeof(int));
11 return (void*)p; //return back
12
13 }
14 void *thread2(void*arg)
15 {
16 printf("thread2 exiting...\n");
17 int *p=(int*)malloc(sizeof(int));
18 *p=2;
19 pthread_exit((void*)p);
20 }
21 void *thread3(void* arg)
22 {
23 while(1){
24 printf("thread3 is running\n");
25 sleep(1);
26 }
27 return NULL;
28 }
29
30 int main()
31 {
32 pthread_t tid;
33 void* ret;
34
35 //thread 1 return
36 pthread_create(&tid,NULL,thread1,NULL);
37 pthread_join(tid,&ret);
38 printf("thread1 return,thread1 id %x,return code:%d\n",tid,*(int*)ret);
39 free(ret);
40
41 //thread2 exit
42
43 pthread_create(&tid,NULL,thread2,NULL);
44 pthread_join(tid,&ret);
45 printf("thread2 return,thread1 id %x,return code:%d\n",tid,*(int*)ret);
46 free(ret);
48 //thread3 cancle by other
49 pthread_create(&tid,NULL,thread3,NULL);
50 sleep(3);
51 pthread_cancel(tid);
52 pthread_join(tid,&ret);
53 if(ret==PTHREAD_CANCELED)
54 printf("thread return,thread id %x,return code:PTHREAD_CANCELED\n",t id);
55 else
56 printf("thread return,thread id %x,return code:NULL\n",tid);
57 }
运行结果:
注:PTHREAD_CANCELED的值为-1。
三. 分离线程
默认情况下,我们创建的新线程是joinable(可结合的),在线程终止时,我们需要对pthread_join操作,否则会造成资源浪费。
线程分离的情况:
(1)自己将自己分离
函数原型:
(2)线程组内的其他线程对目标线程进行分离
函数原型:
int pthread_detach(pthread_self());
需要注意的是:
(1)在分离线程终止时,主线程不需要进行pthread_join操作;
(2)在分离线程异常退出时,仍然会影响进程退出;
(3)在主线程异常退出时,分离线程也会退出。
线程分离代码:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <pthread.h>
6 void *thread_run(void* arg)
7 {
8 pthread_detach(pthread_self());
9 printf("%s\n",(char*)arg);
10 return NULL;
11 }
12
13 int main()
14 {
15 pthread_t tid;
16 if(pthread_create(&tid,NULL,thread_run,"thread1 run...")!=0)
17 {
18 printf("perror");
19 return 1;
20 }
21
22 int ret=0;
23 sleep(1);
24
25 if(pthread_join(tid,NULL)==0)
26 {
27 printf("wait new thread success!\n");
28 ret=0;
29 }
30 else
31 {
32 printf("wait new thread fail!\n");
33 ret=1;
34 }return ret;
35 }
运行结果:
我们可以从运行结果看到在线程分离后,主线程不需要等待分离线程。
最后补充一个小知识点
(1)我们可以通过以下命令来获取线程id
其中LWP为线程id,NLWP为线程组内线程的个数。
(2)也可以通过下面函数获得自身id