2017年年初,我给本身定了一个小小的目标:学习Linux编程,并经过网络来分享本身的学习心得。为了完成这个小小的目标,我开始用经过写文章来记录个人学习心得,但愿在年末时,我能完成24篇Linux相关的学习文档,以实现我这个小小的目标。这是这个系列的第一篇文章,是我对最近学习Linux多线程的总结。编程
咱们来看看维基百科是如何对线程进行定义的:网络
线程(英语:thread)是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并行多个线程,每条线程并行执行不一样的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。多线程
一个进程能够包含多个线程,这些线程有各自的调用栈(call stack),本身的寄存器环境(register context)以及本身的线程本地存储(thread-local storage)。每条线程都是并发执行不一样的任务。由于还没写进程相关的文章,这里我就不进一步说明进程和线程的区别了,我会在进程或者单独的文章中说明它们的异同。并发
线程在其生命周期中有四种状态,分别是就绪、运行、阻塞和终止,下面这个表格解释了这四种状态:函数
状态 | 含义 |
---|---|
就绪 | 线程可以运行,可是在等待可用的处理器 |
运行 | 线程正在运行,在多核系统中,可能同时有多个线程在运行 |
阻塞 | 线程在等待处理器之外的其余条件 |
终止 | 线程从启动函数中返回,或者调用pthread_exit函数,或者被取消 |
线程能够在必定条件下转换其状态,下图显示了线程是如何转换其状态的:学习
要建立线程,咱们使用 pthread_create()
这个函数,函数说明以下:spa
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); args: pthread_t *thread : 指向新线程的线程号的指针,若是建立成功则将线程号写回thread指向的内存空间 const pthread_attr_t *attr : 指向新线程属性的指针 void *(*start_routine) (void *): 新线程要执行函数的地址(回调函数) void *arg : 指向新线程要执行函数的参数的指针 return: 线程建立的状态,0是成功,非0失败
每个成功建立的线程都有一个线程号,咱们能够经过 pthread_self()
这个函数获取调用这个函数的线程的线程号。有一点咱们须要注意,若是须要编译 pthread_XX()
相关的函数,咱们须要在编译时加入 -pthread
。操作系统
要结束一个线程,咱们一般有如下几种方法:线程
方法 | 说明 |
---|---|
exit() | 终止整个进程,并释放整个进程的内存空间 |
pthread_exit() | 终止线程,但不释放非分离线程的内存空间 |
pthread_cancel() | 其余线程经过信号取消当前线程 |
exit()
会终止整个进程,所以咱们几乎不会使用它来结束线程。咱们经常使用 pthread_cancel()
和 pthread_exit()
函数来结束线程。这里,咱们重点看看 pthread_exit()
这个函数:指针
void pthread_exit(void *retval); args: void *retval: 指向线程结束码的指针 return: 无
要结束一个线程,只须要在线程调用的函数中加入 pthread_exit(X)
便可,但有一点须要特别注意:若是一个线程是非分离的(默认状况下建立的线程都是非分离)而且没有对该线程使用 pthread_join()
的话,该线程结束后并不会释放其内存空间。这会致使该线程变成了“僵尸线程”。“僵尸线程”会占用大量的系统资源,所以咱们要避免“僵尸线程”的出现。
在上一部分咱们提到了 pthread_join()
这个函数,在这一节,咱们先来看看这个函数:
int pthread_join(pthread_t thread, void **retval); args: pthread_t thread: 被链接线程的线程号 void **retval : 指向一个指向被链接线程的返回码的指针的指针 return: 线程链接的状态,0是成功,非0是失败
当调用 pthread_join()
时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会从新开始执行。当 pthread_join()
函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(若是被调用线程是非分离的)。这里有三点须要注意:
被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,好比 malloc()
分配的空间。
一个线程只能被一个线程所链接。
被链接的线程必须是非分离的,不然链接会出错。
在上一节中,咱们在谈 pthread_join()
时说到了只有非分离的线程才能使用 pthread_join()
,这节咱们就来看看什么是线程的分离。在Linux中,一个线程要么是可链接的,要么是可分离的。当咱们建立一个线程的时候,线程默认是可链接的。可链接和可分离的线程有如下的区别:
线程类型 | 说明 |
---|---|
可链接的线程 | 可以被其余线程回收或杀死,在其被杀死前,内存空间不会自动被释放 |
可分离的线程 | 不能被其余线程回收或杀死,其内存空间在它终止时由系统自动释放 |
咱们能够看到,对于可链接的线程而而言,它不会自动释放其内存空间。所以对于这类线程,咱们必需要配合使用 pthread_join()
函数。而对于可分离的函数,咱们就不能使用 pthread_join()
函数。
要使线程分离,咱们有两种方法:1)经过修改线程属性让其成为可分离线程;2)经过调用函数 pthread_detach()
使新的线程成为可分离线程。
下面是 pthread_detach()
函数的说明:
int pthread_detach(pthread_t thread); args: pthread_t thread: 须要分离线程的线程号 return: 线程分离的状态,0是成功,非0是失败
咱们只须要提供须要分离线程的线程号,即可以使其由可链接的线程变为可分离的线程。
在上面的几节中,咱们讲了线程的建立,结束和链接。这节咱们来看一个特殊的线程 - 主线程。
在C程序中, main(int argc, char **argv)
就是一个主线程。咱们能够在主线程中作任何普通线程能够作的事情,但它和通常的线程有有一个很大的区别:主线程返回或者运行结束时会致使进程的结束,而进程的结束会致使进程中全部线程的结束。为了避免让主线程结束全部的线程,根据咱们以前所学的知识,有这么几个解决办法:
不让主线程返回或者结束(在 return
前加入 while(1)
语句)。
调用 pthread_exit()
来结束主线程,当主线程 return
后,其内存空间会被释放。
在 return
前调用 pthread_join()
,这时主线程将被阻塞,直到被链接的线程执行结束后,才接着运行。
在这三种方法中,前两种不多被使用,第三种是经常使用的方法。
这篇文章主要介绍了线程的基本概念,线程的生命周期和状态,线程的建立、结束、链接、分离以及主线程。在下一篇中,我将介绍多线程中的重点 - 同步。
若是以为本文对你有帮助,请多多点赞支持,谢谢!