线程对于Linux后台程序员来说并不陌生,线程带给我们并发能力的提升,也提高了软件开发和问题定位的难度,本文
尝试结合GlibC 代码, 对POSIX的线程做一个简单说明,重点介绍线程的创建,释放和连接上需要注意的问题。
多进程和多线程的都只有一个目的,并行处理,提高CPU利用率。相比进程,线程的优势体现在以下几个方面:
1.线程创建销毁开销小于进程
2.线程上下文切换开销小于进程
3.线程间通信优于进程
针对第三点,这针对第三点,这是一个见仁见智的问题,一个进程下的所有线程共享内存地址,所以线程间的通信可
以很随意,但是为了维护对公共资源的有序读写,又引入了锁,信号量等机制,这些工具一旦使用不当会出现死锁,
临界区竞争等问题,所以多线程的模式也提高了编程难度和定位问题的复杂度.
Linux的线程也叫 轻量级线程(LWP, light weight process),来自 Native POSIX Thread Library (NPTL)库的实现
,该库在1995年被POSIX.1c定为标准API。每个线程拥有自己的task_struct,所以用独立的堆栈空间,能独立的够被
CPU调度。
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数性质:
分别为,线程ID,线程属性,执行函数入口,线程执行函数参数.
POSIX 线程的第一个参数 thread 类型为 pthread_t,本质是long int 型。用于返回进程中唯一标识ID。因为该ID只在
进程中唯一识别线程,所以可称为局部线程ID(相对后面用于CPU调度,pid_t类型的全局线程ID而言)。在NPTL
中,thread 存储的是线程描述结构(struct pthread)的地址。
由于栈地址是复用的,所以thread_t* thread的值也会重复,如果主线程创建了一个线程后退出,那么再创建的线程
其 thread 值会和前面的线程一样的
POSIX 线程是CPU的调度实体,所以CPU需要一个唯一ID标示不同的线程,这就是pid_t类型的全局线程ID。Linux
的进程由一个或多个线程组成,所以进程也叫线程组,线程组内的第一个线程称为主线程,该线程ID就是线程组
ID,也是进程ID。而之后创建的线程,其线程组ID(进程ID)不变,每个线程有独立的,pid_t类型的LWP ID,CPU
就是根据该ID获得线程上下文信息(struct_task)实现线程调度.
线程的栈:
进程的内存分布布局中,用户空间分为我们熟知的代码段,已初始化数据段,未初始化数据段,堆,栈等等. 随着
进程启动,用户内存空间单独划分一段区域为主线程栈,主线程栈的大小可以动态变化,从高地址向下扩展,随后
创建的线程栈大小则是固定的,线程栈通过mmap方式在内存映射区划分内存,线程栈大小可以通过ulimit -s
查看,默认为8192kb大小,线程栈之间有一个保护区,该区域被访问触发会发信号告警,保护区默认大小是一个
内存页(4096字节).
线程退出:
#include
void pthread_exit(void *retval);
其中参数retval是要退出后要上报的结果,其他线程可以通过pthread_join得到该值.
需要注意的是,retval要求指向的地址不会因为线程退出而失效,这说明用线程变量存储返回值是不靠谱的.
线程的连接:
#include
int pthread_join(pthread_t thread, void **retval);
pthread_join函数用于取出其他线程退出后上报的结果,只能针对可连接的线程(joinable)线程使用,而且每个线程
只能被;连接一次。 参数thread是上下文提到的局部线程ID,线程不能自己连接自己,既不能在thread填写自己的
局部线程ID.
pthread_join 的作用和进程中的waitpid非常像,waitpid是父进程为子进程“收尸”,pthread_join也是一个线程为另
一个线程“收尸”。父进程如果不进行wait操作,子进程退出后就会成为僵尸进程,线程也一样,可连接(joinable
)的线程在退出后如果没有其他线程调用pthread_join 接住它的退出状态,那它也同样不会释放线程的资源。调
用pthread_join的线程在接收到返回状态前会陷入阻塞。
和进程的wait不同,父进程可以等待任何一个子进程退出,但是线程必须明确指定要等待的线程id。这是因为线程
之间没有父线程和子线程层级关系。一个进程也就是一个线程组,里面只有一个主线程,其他的都是衍生出来的
线程,人人平等,即便主线程调用pthread_exit退出了(不建议这么做),进程还是能继续运行。
线程分离:
如果线程觉得退出也没什么遗言,可以把线程属性设置为分离状态,线程的分离状态可以在创建线程时配置属性
来调整,pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED)也可以在任何线程中调用
pthread_detach函数来调整该属性
#include
int pthread_deatch(pthread_t thread);
较为普遍的做法是线程内部自己修改分离状态。
pthread_detach(pthread_self())
线程处于分离状态表示退出后没有任何遗言,随着线程结束马上交出栈资源,所以也无需其他线程连接它。
pthread_join 分离状态的线程,会返回错误 EINVAL。