Linux线程简说 linux
尹德位 2015 西安 nginx
本文内容臃肿,做者尽力在描述本身对线程的感悟,表达能力有限,敬请读者去其糟粕。 数据库
系统环境: RedHat Enterprise Linux 7.0 x86_64 多线程
内核版本: linux-4.1.0 并发
Glibc版本: glibc-2.22 函数
IT名言:进程是资源分配的基本单位。线程是程序调度的基本单位。 性能
目录: 学习
1.线程概念 spa
2.线程建立 操作系统
3.线程特性
4.线程控制
5.线程同步
6.线程应用
<<正文>>
1.线程概念
众所周知,进程就是一个关于某数据集合的运行过程。现代操做系统理论体系是创建在进程基础上的。一切功能性程序,http服务,nginx服务,Oracle数据库等等都是以进程为单位运行的。进程的运行除了须要分配内存装载代码段(.txt段)外,还须要分配必要的资源才能运行。
有时候咱们须要利用CPU多核的优点实现代码的并行执行来提升效率,而恰巧这些多份并行执行的代码是能够共享资源,这种状况下采用进程技术来实现多任务程序就显得臃肿笨拙了。因而线程技术就在千呼万唤中诞生了。
线程,是轻量级进程。除了必要的栈、上下文等资源外,线程不须要其余太多额外资源便能运行。其精巧高效的特性是软件设计中常采用线程来实现多任务的主要缘由。比起进程,线程更便于灵活调度运行。于是现在的操做系统都以线程为单位来调度,以极大提升系统的运行效率。
尽管线程有许多优点,但并不是能够取代进程。下面讲述两者的区别,以便读者理解其应用场景。
区别1:独立性
进程是彻底独立的,进程之间互不干扰对方的内存空间(也称进程空间),亲属进程建立的多个子进程与其父进程之间依然保持独立性,换言之,若任一进程出现故障,不影响其余进程运行。而线程与之相比则不一样,处于同一进程空间中的多个子线程之间共享父线程(父进程)的资源,任一线程若出现故障致使系统信号触发,则整个进程空间都受牵连。好比某一线程访问非法内存地址,则SIGSEGV信号将终结整个进程。
区别2:做用范围
进程是全系统范围内可见的,经过ps命令能够看到任何正在运行的进程信息。固然线程也能够看到,但不一样的是,线程只能与同一进程空间的其余线程之间保持联系,跨越进程的线程之间没有创建通讯的必要。由于线程只是共享父进程数据资源,跨越进程的线程由于加工数据资源的对象处于不一样空间而失去意义。
区别3:正文段区间
子进程的正文段与父进程共享,且拥有所有执行区间,若没有严格逻辑限制,则子进程将从其建立开始,与父进程执行同一代码副本直到结束。而线程的运行期间相较而言短了许多,只是从线程入口函数开始,到其函数结束线程自动终止。
区别4:项目代码体系
进程建立的系统调用fork由内核提供;线程库函数由glibc(运行于内核之上的库)提供。
2.线程建立
几乎全部的Linux操做系统都提供线程技术,由于线程的实现由Linux内核提供。
线程建立函数:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
该函数功能是建立一个新的线程,其函数体在标准glibc库中的代码能够看到:
http://ftp.gnu.org/pub/gnu/glibc/glibc-2.22.tar.gz
nptl/pthread_create.c :
int __pthread_create_2_1 (newthread, attr, start_routine, arg)
pthread_t *newthread;
const pthread_attr_t *attr;
void *(*start_routine) (void *);
从源码得知,线程真身的建立是调用了create_thread函数:
/* Create the thread. We always create the thread stopped
so that it does not get far before we tell the debugger. */
retval = create_thread (pd, iattr, true, STACK_VARIABLES_ARGS,
&thread_ran);
继续追踪create_thread:
sysdeps/unix/sysv/linux/createthread.c :
const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
| CLONE_SIGHAND | CLONE_THREAD
| CLONE_SETTLS | CLONE_PARENT_SETTID
| CLONE_CHILD_CLEARTID
| 0);
由此发现,线程的建立实际上仍是clone技术的封装。换句话说,内核提供的clone系统调用能够建立线程,而POSIX线程库也是经过clone技术实现的线程。Linux的NPTL线程技术兼容了POSIX标准,因此在描述上不作区分。
可经过Linux的man手册页来查看 : man clone
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
综上所述,不管是调用clone建立线程,仍是调用POSIX线程库的pthread_create建立一个线程,其本质都是同样。
3.线程特性
许多教科书在描述线程和进程的时候都不会刻意区分,由于在执行单位上两者没有区别。内核对线程和进程的调度,其实是统一的。咱们知道,每一个进程都有一个进程控制信息结构,维护在内核数据区。该结构为 struct task_struct ,每个进程都会有一个惟一的结构体与之对应。
该结构体的原型定义在内核代码的 include/linux/sched.h中。
线程,一样具备该结构体。也即每一个线程也都有一个彻底独立的结构体节点。
那么,问题就出现了:
进程的管理都是以其标识进程ID(即PID)来实现的,莫非线程也具备PID ?
答案是确定的。
接下来咱们就详细讨论下线程的标识ID。
首先,咱们来看看,命令行里看看多进程和多线程的显示结果。
经过ps auxH 或者 pstree –ap 来查看(读者可本身编写一个建立线程的程序):
进程号35985的进程有一个ID为35986的子进程。还发现它的名字还有一对“{ }”,其实35986就是一个线程。普通进程身份的子进程的名字不带大括号。
再经过ps命令咱们再看看:
能够看出,两个线程的PID竟然相同。
这就是线程的ID特性,每个线程对外而言都表达其父进程的ID。换句话说,一个进程建立的全部子线程,都是以同一进程身份(即父进程)运行,而其内部依然是并发运行。
ps命令的H参数能够看到子线程,而且其显示效果上与子进程并没有太多区别,但,若用kill命令终止其中任何一个子线程,则整个进程的所有线程(包括父线程/父进程)都将终结。这就是与多进程程序不一样的地方,若是是用kill终止一个子进程,则其余进程不受任何影响。从这点来讲,多线程程序的独立性就差一些,若是某个线程资源访问失误(访问空指针)致使SIGSEGV信号触发的话,牵连甚广。
好了,上面说了线程的具备进程特性的PID,下面说说线程特有的TID。
每个线程除了拥有被调度控制用的PID外,还有一个pthread_t类型的TID,该ID实际是一个long类型的数据区,存储着每一个线程的内部标识ID。这个TID只在同一进程空间的内有意义,离开进程空间该ID没有任何做用。
换言之,TID是父进程用来控制子线程时采用的线程标识。
多线程程序的在线调试也比多进程程序简单,因其共享同一进程空间。
可经过info thread查看各线程的编号,并经过thread +编号 直接切换到该线程。
(bt命令能够查看线程的栈数据。)
事实上,从线程的栈能够看出,线程是clone产生的,以下图:
4.线程控制
线程控制通常指父线程对子线程的控制,命令行实际不能对子线程直接进行终止操做(会波及整个进程)。因此线程的控制技术此处就再也不赘述,提供几个关键词供读者查阅学习:分离属性,线程取消点,线程回收,线程信号。
5.线程同步
线程大量被采用的一大缘由是其精巧高效,运行调度很方便。那么,若多个线程间有数据互斥操纵的场合下,其同步技术是否是同样优越于进程呢?
答案是确定的。
线程的同步技术大致有这样几种(注:线程间独有,进程间不可采用):
互斥量(即线程锁),读写锁,自旋锁,条件变量。
线程锁只用于同一进程空间内的所有子线程中,跨越进程的线程间通常不采用线程锁来同步。咱们知道,进程间的同步技术有PV操做(信号量技术),那线程锁是否是就比信号量快呢?
答案仍是确定的。
信号量是一种特殊系统资源,它是全系统可见的。所以要实现互斥,在进程P、V操做时就必须实现原子操做。对于内核而言,原子操做自己是一种负担较重的处理,而且原子操做会下降内核的并发性能。于是,P、V操做都须要内核提供操做方法,即系统调用。用户程序若频繁的采用系统调用,则会下降程序性能,还会增长内核上下文切换开销,总的来讲,既损失了自身效率,还加剧了内核负担。
与之相比,线程锁则优越许多。由于线程锁的做用空间很小,只限于单个进程内,于是线程锁的实现就进化到了全局变量。即,线程锁就是一个全局控制变量,这样在对线程锁进行操做的时候就无需内核帮助,也避免访问内核数据区提升了性能。再者,做用域变小了,参与资源竞争的个体(进程/线程)就减小了,使得多任务程序能够快速获得互斥资源。
总的一句话,线程的同步技术快于进程。
6.线程应用
说了这么多的线程优点,那是否是全部的多任务环境均可以采用线程来替代进程设计呢?
答案是不能够。
进程和线程,两者各有优点,应在不一样的场景采用不一样方式。
线程虽然运行时无需过多资源,但在须要独立控制的任务里,它又变成了缺陷。好比当今流行的nginx服务,它采用的是进程池技术,之因此是进程而不是线程,就是考虑了任务独立性的要求。对于数据独立性要求低,或者说是专门处理共享数据,只是为了提升代码并发执行效率的话,采用线程就是优选之举了。
限于篇幅,本文就此停笔,宏观上的线程特性请参考:
http://my.oschina.net/cnyinlinux/blog/367910
<<END>>