线程&进程的概念
线程:一个程序里的一个执行路线(执行流)称为线程。即:线程是一个进程内部的控制序列。
进程:程序的一次执行实例,即:担当分配系统资源(CPU时间,内存)的实体。
线程&进程关系
- 进程是操作系统分配资源的最小单位
- 线程是CPU调度的基本单位
- 同一个进程中的多个线程可以并发进行,在多处理器的情况下可以同时同时并行。
进程&线程的共享
- 同一地址空间:如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
线程的优缺点
优点:
1. 创建一个新线程的代价要比创建一个新进程小的多
2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3. 线程占用的资源比进程少
4. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
5. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
6. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
缺点:
1. 健壮性降低
2. 缺乏访问控制
3. 编程难度提高
线程创建
功能:创建一个新的线程
原型:
#include <pthread.h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr, void *(*star t_routine)(void*), void *arg);
参数:
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
线程创建时并不能保证哪个线程会先运行。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
pthread函数在调用失败时通常会返回错误码,不需要依赖那些随着函数执行不断变化的全局变量,这样可以吧错误的范围限制在引起出错的函数中。
线程ID&进程ID
- 线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。
- 没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下⼦子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID。
由此Linux内核引⼊入了线程组的概念。
多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符 (task_struct)与之对应。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5 #include <error.h>
6
7 pthread_t mtid;
8
9 void *child_func()
10 {
11 while(1)
12 {
13 printf("子线程\n");
14 sleep(1);
15 }
16 return NULL;
17 }
18 int main()
19 {
20 int ret=pthread_create(&mtid,NULL,child_func,NULL);
21 if(ret!=0)
22 perror("can't create thread"),exit(1);
23 while(1)
24 {
25 printf("主线程\n");
26 sleep(1);
27 }
28 return 0;
29 }
可以看出上面tid进程是多线程的,进程ID为16947,进程内有2个线程,线程ID分别为16947,16948。
正如我们期望的那样:
- 线程组内,线程的进程ID一样,线程ID不同
- 线程组内的第一个线程,在用户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,group_leader指针则指向自身,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。
线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。
线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。
线程ID及进程地址空间布局
- pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该程。
- pthread_ create函数产生并标记在第一个参数指向的地址中的线程ID中,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID
线程终止
如果需要只终止某个线程而不是终止整个进程,可以有三种方法:
线程可以调用pthread_exit终止自己。(主动)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5 #include <error.h>
6
7 pthread_t mtid;
8
9 void *child_func()
10 {
11 while(1)
12 {
13 printf("子线程\n");
14 sleep(1);
15 pthread_exit((void *)mtid);
16 }
17 return NULL;
18 }
19 int main()
20 {
21 int ret=pthread_create(&mtid,NULL,child_func,NULL);
22 if(ret!=0)
23 perror("can't create thread"),exit(1);
24 while(1)
25 {
26 printf("主线程\n");
27 sleep(1);
28 }
29 return 0;
30 }
从线程函数return。这种方法对于主线程不适用,从main函数return相当于调用exit。(主动)
9 void *child_func()
10 {
11 while(1)
12 {
13 printf("子线程\n");
14 sleep(1);
15 //pthread_exit((void *)mtid);
16 return (void *)NULL;
17 }
18 return NULL;
19 }
20 int main()
21 {
22 int ret=pthread_create(&mtid,NULL,child_func,NULL);
23 if(ret!=0)
24 perror("can't create thread"),exit(1);
25 while(1)
26 {
27 printf("主线程\n");
28 sleep(1);
29 return 0;
30 }
一个线程可以调用pthred_cancel终止同一进程中的另一个线程
20 int main()
21 {
22 int ret=pthread_create(&mtid,NULL,child_func,NULL);
23 if(ret!=0)
24 perror("can't create thread"),exit(1);
25 while(1)
26 {
27 printf("主线程\n");
28 sleep(1);
29 pthread_cancel(mtid);
31 }
32 return 0;
33 }
int main()
21 {
22 int ret=pthread_create(&mtid,NULL,child_func,NULL);
23 if(ret!=0)
24 perror("can't create thread"),exit(1);
25 while(1)
26 {
27 printf("主线程\n");
28 sleep(1);
29 pthread_cancel(pthread_self());
线程等待和分离
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复用刚才退出线程的地址空间。
这样会造成资源浪费!!!
功能:等待线程结束
原型:
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向一个无类型指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得 到的终止状态是不同的:
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED。
- 如果thread线程是自己调用pthread_exit 终止的 ,valueptr所指向的单元存放的是传给pthread_exit 的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <unistd.h>
5 #include <error.h>
6
7 pthread_t tid;
8
9 void *thread1(void *arg)
10 {
11 printf("thread 1 return\n");
12 return (void *)1;
13 }
14
15 void *thread2(void *arg)
16 {
17 printf("thread 2 pthread_exit\n");
18 pthread_exit((void *)NULL);
19 }
20
21 void *thread3(void *arg)
22 {
23 while(1)
24 {
25 printf("thread 3 is running...\n");
26 sleep(1);
27 }
28 return (void *)NULL;
29 }
30 int main()
31 {
32 int ret;
33 void *tret;
34
35 ret=pthread_create(&tid,NULL,thread1,NULL);
36 if(ret!=0)
37 perror("pthread_create1"),exit(1);
38 ret=pthread_join(tid,&tret);
39 if(ret!=0)
40 perror("pthread_join1"),exit(1);
41 printf("thread 1 return, thread 1 id = %X,return code:%ld\n",tid,(long)tret);
42
43 ret=pthread_create(&tid,NULL,thread2,NULL);
44 if(ret!=0)
45 perror("pthread_create2"),exit(1);
46 ret=pthread_join(tid,&tret);
47 if(ret!=0)
48 perror("pthread_join2"),exit(1);
49 printf("thread 2 pthread_exit, thread 2 id = %X,return code:%ld\n",tid,(long)tret);
50
51 ret=pthread_create(&tid,NULL,thread3,NULL);
52 if(ret!=0)
53 perror("pthread_create3"),exit(1);
54 sleep(3);
55 pthread_cancel(tid);
56 pthread_join(tid,&tret);
57 if(tret==PTHREAD_CANCELED)
58 printf("thread 3 return, thread 3 id = %X,return code:PTHREAD_CANCELED\n",tid);
59 else
60 printf("thread 3 return, thread 3 id = %X,return code:NULL",tid);
61 return 0;
62 }
调用线程将一直阻塞,直到指定的线程调用pthread_exit 、从启动例程中返回或者被取消。如果线程简单地从他的启动例程返回,value_ptr就包含返回码。如果线程被取消,由value_ptr指定的内存单元就设置为PTHREAD_CANCELED。
分离线程
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
- 在线程被分离后,我们不能用pthread_join函数等待它的终止状态,因为对分离状态的线程调用pthread_join会产生未定义行为。
#include <pthread.h>
//可以是线程组内其他线程对目标线程进行分离
int pthread_detach(pthread_t thread);
//也可以是线程自己分离
int pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run( void * arg )
{
pthread_detach(pthread_self());
printf("%s\n", (char*)arg);
return NULL;
}
int main( void )
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 )
{
printf("create thread error\n");
}
int ret = 0;
sleep(1);
if ( pthread_join(tid, NULL ) == 0 )
{
printf("pthread wait success\n");
ret = 0;
}
else
{
printf("pthread wait failed\n");
ret = 1;
}
return ret;
}