Linux应用编程之——线程
上一篇文章,刚讲完进程,这边就直接说一下线程吧。附带一个链接
Linux应用编程之——进程
线程介绍:线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程。在Linux系统中,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程可以拥有多个线程,它还可以同时使用多个cpu来执行各个线程,以达到最大程度的并行,提高工作的效率;同时,即使是在单cpu的机器上,也依然可以采用多线程模型来设计程序,使设计更简洁、功能更完备,程序的执行效率也更高。
上面的这些概念我们不难得出一个非常重要的结论:线程的本质是一个进程内部的一个控制序列,它是进程里面的东西,一个进程可以拥有一个进程或者多个进程,那么它们的关系就如图所示:
那么进程跟线程有什么区别呢???
从很多Linux的书籍我们都可以这样子描述进程和线程的:进程是资源管理的最小单位,线程是程序执行的最小单位。
没错,这些确实是线程与进程之间的区别,这个描述非常言简意赅,在操作系统设计上,从进程演化出线程,最主要的目的就是减小进程上下文切换开销,这又是怎么一回事呢?在前面的文章已经提到,进程是资源管理的最小单位,那么每个进程都拥有自己的数据段、代码段和堆栈段,这必然就造成了进程在进行切换时都需要有比较复杂的上下文切换等动作,因为要保存当前进程上下文的内容,还要恢复另一个进程的上下文,如果是经常切换进程的话,这样子的开销就过于庞大,因为在进程切换上下文时,需要重新映射虚拟地址空间、进出OS内核、寄存器切换,还会干扰处理器的缓存机制,因此为了进一步减少CPU在进程切换时的额外开销,因此Linux进程演化出了另一个概念——线程。
总的来说:
一个程序至少有一个进程,一个进程至少有一个线程。
线程使用的资源是进程的资源,进程崩溃线程也随之崩溃。
线程的上下文切换,要比进程更加快速,因为本质上,线程很多资源都是共享进程的,所以切换时,需要保存和切换的项是很少的。
创建线程
在讲解线程编程之前,先了解一个标准:可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX),POSIX是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写,而X则表明其对Unix API的传承。
在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread ,它是一套通用的线程库,是由 POSIX 提出的,因此具有很好的可移植性,我们学习多线程编程,就是使用它,必须包含以下头文件:
#include <pthread.h>
除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread 选项。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数:
thread:指向线程标识符的指针。
attr:设置线程属性。
start_routine:start_routine是一个函数指针,指向要运行的线程入口。
arg:运行线程时传入的参数。
pthread_create()函数是用于创建一个线程的,创建线程实际上就是确定调用该线程函数的入口点,在线程创建后,就开始运行相关的线程函数。若线程创建成功,则返回0。若线程创建失败,则返回对应的错误代码。
线程属性
Linux中线程属性结构如下:
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
structsched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
下面简单讲解一下与线程属性相关的API接口:
API 描述
pthread_attr_init() 初始化一个线程对象的属性
pthread_attr_destroy() 销毁一个线程属性对象
pthread_attr_getaffinity_np() 获取线程间的CPU亲缘性
pthread_attr_setaffinity_np() 设置线程的CPU亲缘性
pthread_attr_getdetachstate() 获取线程分离状态属性
pthread_attr_setdetachstate() 修改线程分离状态属性
pthread_attr_getguardsize() 获取线程的栈保护区大小
pthread_attr_setguardsize() 设置线程的栈保护区大小
pthread_attr_getscope() 获取线程的作用域
pthread_attr_setscope() 设置线程的作用域
pthread_attr_getstack() 获取线程的堆栈信息(栈地址和栈大小)
pthread_attr_setstack() 设置线程堆栈区
pthread_attr_getstacksize() 获取线程堆栈大小
pthread_attr_setstacksize() 设置线程堆栈大小
pthread_attr_getschedpolicy() 获取线程的调度策略
pthread_attr_setschedpolicy() 设置线程的调度策略
pthread_attr_setschedparam() 获取线程的调度优先级
pthread_attr_getschedparam() 设置线程的调度优先级
pthread_attr_getinheritsched() 获取线程是否继承调度属性
pthread_attr_getinheritsched() 设置线程是否继承调度属性
初始化一个线程对象的属性
int pthread_attr_init(pthread_attr_t *attr);
若函数调用成功返回0,否则返回对应的错误代码。
attr:指向一个线程属性的指针
销毁一个线程属性对象
int pthread_attr_destroy(pthread_attr_t *attr);
若函数调用成功返回0,否则返回对应的错误代码。
attr:指向一个线程属性的指针
线程的分离状态
什么是线程的分离状态呢?在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
总而言之:线程的分离状态决定一个线程以什么样的方式来终止自己。
进程中的线程可以调用以下函数来等待某个线程的终止,获得该线程的终止状态,并收回所占的资源,如果对线程的返回状态不感兴趣,可以将rval_ptr设置为NULL。
int pthread_join(pthread_t tid, void **rval_ptr);
除此之外线程也可以调用以下函数将此线程设置为分离状态,设置为分离状态的线程在线程结束时,操作系统会自动收回它所占的资源。设置为分离状态的线程,不能再调用pthread_join()等待其结束。
int pthread_detach(pthread_t tid);
如果一个线程是可结合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程,同时意味着该线程的退出值可以被其他线程获取。因此,如果不需要某条线程的退出值的话,那么最好将线程设置为分离状态,以保证该线程不会成为僵尸线程。
如果在创建线程时就知道不需要了解线程的终止状态,那么可以通过修改pthread_attr_t结构中的detachstate属性,让线程以分离状态启动,调用函数如下:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
如果想要获取某个线程的分离状态,那么可以通过以下函数:
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
线程退出
在线程创建后,系统就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这是线程的一种隐式退出的方法,这与我们进程的退出差不多,进程完成工作后就会退出。而另一种退出线程的方法是使用pthread_exit()函数,让线程显式退出,这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理,这是因为exit()函数的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都会被退出,因此在线程中只能调用线程退出函数pthread_exit()而不是调用进程退出函数exit()。
void pthread_exit(void *retval);
线程实验
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *test_thread(void *arg)
{
int num = (unsigned long long)arg; /** sizeof(void*) == 8 and sizeof(int) == 4 (64 bits) */
printf("arg is %d\n", num);
pthread_exit(NULL);
}
int main(void)
{
pthread_t thread;
void *thread_return;
int arg = 520;
int res;
printf("start create thread\n");
res = pthread_create(&thread, NULL, test_thread, (void*)(unsigned long long)(arg));
if(res != 0)
{
printf("create thread fail\n");
exit(res);
}
printf("create treads success\n");
printf("waiting for threads to finish...\n");
res = pthread_join(thread, &thread_return);
if(res != 0)
{
printf("thread exit fail\n");
exit(res);
}
printf("thread exit ok\n");
return 0;
}
运行结果如下
start create thread
create treads success
waiting for threads to finish…
arg is 520
thread exit ok