一、线程概念
1、什么是线程
(1)线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
(2)线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
(3)一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
(4)直白地说,一个线程是一个进程的内部控制序列,每一个进程至少有一个线程。
2、线程的基本操作
(1)派生:线程在进程的内部,它既可以由进程派生,也可以由线程派生;
(2)阻塞(block):如果一个线程在执行的过程中需要等待某个事件发生,则线程被阻塞
(3)激活(Unblock):如果阻塞线程的事件发生,则线程被激活进入就绪队列中
(4)调度:线程从就绪队列中进入执行状态
(5)结束:如果一个线程执行结束,它的寄存器集合以及上下文堆栈将被释放
3、线程的优缺点
1)线程优点
(1)创建一个新线程的代价要比进程小很多
(2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
(3)线程占用的资源要⽐进程少很多
(4)能充分利用多处理器的可并行数量
(5)在等待慢速I/O操作结束的同时,程序可执⾏其他的计算任务计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
(6)I/O密集型应⽤,为了提⾼性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
2)线程的缺点
(1)性能损失:当计算密集型线程的数量比可用的处理器多时,会因为产生额外的同步和调度开销,但是可用资源没有改变,造成性能损失
(2)健壮性低:编写多线程需要更全⾯深⼊的考虑,在一个多线程序里,因时间分配上的细微偏差或者因共享了不该共享的变量⽽造成不良影响的可能性是很⼤的,换句话说线程之间是缺乏保护的。
(3)缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会直接影响整个进程
(4)编程难度大:编写与调试一个多线程程序比一个单线程程序要复杂很多
4、线程与进程的区别
在上面的关于线程的概念介绍中,我们会发现经常将线程与进程联系在一块,难么进程和线程到底有什么区别呢,下面将详细介绍:
(1)空间分配结构和安全性上:进程拥有独立的堆栈空间和数据段,所以每当要启动一个新的进程,必须给他分配独立的地址空间,建立众多的表来维护代码段、数据段和堆栈段,这对进程来说十分“奢侈”,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高。但也正是因为进程拥有独立空间的特性,所以进程的安全性更高,当一个进程出现问题时不会影响其他进程;而当一个线程出现异常终止时,整个进程也将被结束,所以其他线程也受到了影响。
(2)体现在通信机制上面:正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。
(3)CPU系统上:线程使得CPU系统更加有效,因为操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
(4)程序结构上:线程有利于改善程序的结构,当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,不过在需要用的时候还是要用
(5)进程是资源的分配和调度的基本单元,而线程是CPU调度的基本单元
5、线程和进程选取的情况
(1)需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
(2)线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应
(3)因为对CPU系统的效率使用上线程更占优
(4)并行操作时使用线程
(5)需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
二、线程控制
1、POSIX线程库
(1)与线程有关的函数构成了⼀个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
(2)要使用这些函数库,要通过引入头⽂件 #include
2、创建线程
1)pthread_create函数
函数原型:
函数解释:
(1)功能:创建一个新的线程
(2)参数:
thread:返回线程ID
attr:设置线程的属性,为NULL便是默认属性
start_routine:函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
(3)返回值:成功返回0,失败返回错误码
2)实例:创建一个线程
代码实现部分:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.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'm thread!\n");
13 sleep(1);
14 }
15 }
16
17 int main(void)
18 {
19 pthread_t tid;
20 int ret;
21 if((ret=pthread_create(&tid,NULL,rout,NULL))!=0)
22 {
23 fprintf(stderr,"pthread_create: %s\n",strerror(ret));
24 exit(EXIT_FAILURE);
25 }
26 int i;
27 for( ; ; )
28 {
29 printf("I'm main thread!\n");
30 sleep(1);
31 }
32 }
运行结果:
因为pthread.h不是Linux默认的库,所以在编译的时候要用gcc -lpthread mythread.c 进行编译,否则就会编译出错。
3、进程ID和线程ID
linux 中的线程是基于进程实现的,每个线程都会有一个进程对应,通过gettid()可以获取到该进程id。 另外,通过pthread_self()获取到的是POSIX thread id。
可以用 ps -eLf | head -1 && ps -eLf | grep -v grep 查看线程ID:
在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有⾃己的进程描述符(task_struct结构体)。
对应的task_struct结构体如下:
struct task_struct {
...
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader;
...
struct list_head thread_group;
...
};
在上面进程ID为1599的进程中有两个线程,属于多线程,我们把它称为线程组,线程组内的第一个线程,在用户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第⼀个线程时,会将线程组的ID的值设置成第⼀个线程的线程ID,group_leader指针则指向⾃⾝,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。
注意:强调一点,线程和进程不一样,进程有父进程的概念,但在线程组⾥⾯,所有的线程都是对等关系
4、线程终止
1)线程终止的三种方法(只终止线程,不终止整个进程)
(1)从线程函数return。这种方法对主线程不适用,从main函数return相当于调⽤exit。
(2) 线程可以调用pthread_ exit终⽌止⾃己。
(3)⼀个线程可以调⽤pthread_ cancel终⽌同一进程中的另一个线程
2)pthread_exit函数
函数原型:
函数说明:
(1)功能:线程终止
(2)参数:retval 是一个指针,不能指向一个临时变量
(3)返回值:无返回值
3)pthread_cancel函数
函数原型:
函数解释:
(1)功能:取消一个执行中的线程
(2)参数:thread表示线程ID
(3)返回值:成功返回0,失败返回错误码
测试代码:
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 #include<pthread.h>
6 #include<sys/types.h>
7
8 void *Thread(void*arg)
9 {
10 int i;
11 for(i=0;i<5;i++)
12 {
13 printf("The thread is :%s\n",(char*)arg);
14 }
15 pthread_exit(NULL);
16 }
17
18 int main()
19 {
20 char *str="hello";
21 char *str1="xi'an";
22
23 pthread_t tid;
24 pthread_t tid1;
25
26 int ret;
27 if((ret=pthread_create(&tid,NULL,Thread,(void*)str))!=0)
28 {
29 exit(-1);
30 }
31 if((ret=pthread_create(&tid1,NULL,Thread,(void*)str1))!=0)
32 exit(-1);
33 pthread_exit(NULL);
34 }
运行结果:
5、线程等待
(1)为什么需要线程等待?
a、已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
b、创建新的线程不会复用刚才退出线程的地址空间
(2)pthread_join函数
函数原型:
函数解释:
(1)功能:等待线程结束
(2)参数:thread—线程ID
retval:指向一个指针,后者指向线程的返回值
(3)返回值:成功返回0,失败返回错误码
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
(1) 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
(2)如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元⾥存放的是常数PTHREAD_ CANCELED。
(3)如果thread线程是⾃⼰调用pthreadexit 终止的 ,valueptr所指向的单元存放的是传给pthread_exit的参数。
(4) 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ ptr参数。
(3)实例代码
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 {
9 printf("thread1 returnning....\n");
10 int *p=(int*)malloc(sizeof(int));
11 *p=1;
12 return (void*)p;
13 }
14
15
16 void *thread2(void*arg)
17 {
18 printf("thread2 returnning....\n");
19 int *p=(int*)malloc(sizeof(int));
20 *p=2;
21 return (void*)p;
22 }
23
24 void *thread3(void*arg)
25 {
26 printf("thread3 returnning....\n");
27 int *p=(int*)malloc(sizeof(int));
28 *p=3;
29 return (void*)p;
30 }
31
32 int main(void)
33 {
34 pthread_t tid,tid1,tid2;
35 void*ret;
36 pthread_create(&tid,NULL,thread1,NULL);
37
38 pthread_create(&tid1,NULL,thread2,NULL);
39 pthread_create(&tid2,NULL,thread3,NULL);
40
41 pthread_join(tid,&ret);
42 pthread_join(tid1,&ret);
43 pthread_join(tid2,&ret);
44 }
运行结果:
6、分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进⾏pthread_join操作,否则⽆法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,⾃动释放线程资源。
(1)pthread_detach函数
函数原型:
函数原型:
(1)功能:用来分离可结合线程tid
(2)参数:thread表示线程ID,也可以是pthread_self(),即就是分离线程自己
(3)返回值:成功返回0,失败返回错误码
实例验证:
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 {
9 pthread_detach(pthread_self());
10 printf("%s\n",(char*)arg);
11 return NULL;
12 }
13
14 int main(void)
15 {
16 pthread_t tid;
17 if(pthread_create(&tid,NULL,thread_run,"thread run...")!=0)
18 {
19 printf("create thread error\n");
20 return 1;
21 }
22 int ret=0;
23 sleep(1);
24
25 if(pthread_join(tid,NULL)==0)
26 {
27 printf("pthread wait success\n");
28 ret=0;
29 }
30 else
31 {
32 printf("pthread wait failed\n");
33 ret=1;
34 }
35 return ret;
36 }
运行结果:
以上就是关于线程的一些常见操作,对于线程的同步与互斥将在下一篇博客中详细介绍。