在linux内核,线程与进程的区别很小,或者说内核并无真正所谓单独的线程的概念,进程的建立函数是fork,而线程的建立是经过clone实现的。linux
而clone与fork都是调用do_fork(),差别以下:api
1 SYSCALL_DEFINE0(fork) 2 { 3 return do_fork(SIGCHLD, 0, 0, NULL, NULL); 4 } 5 6 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 7 int __user *, parent_tidptr, 8 int __user *, child_tidptr, 9 int, tls_val) 10 { 11 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); 12 }
实际上就是内核开放大部分参数和do_fork接口来建立线程,看clone的官方解释:异步
The main use of clone() is to implement threads: multiple threads of control in a program that run concurrently in a shared memory space.
因此接下来参考glibc 2.25版本的pthread_create来看看进程和线程的具体实现差别在哪里:函数
1 const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM 2 | CLONE_SIGHAND | CLONE_THREAD 3 | CLONE_SETTLS | CLONE_PARENT_SETTID 4 | CLONE_CHILD_CLEARTID 5 | 0);
显而易见clone_flags的差异很是大.工具
下面再来经过这些flags的做用来区分进程和线程的特性,细数以前先看看do_fork的实现,发现有意思的是,ptrace和perf这2个调试工具也是进程建立的时候初始化的.spa
一、CLONE_VM线程
首先线程不能脱离父进程独立存在,因此它须要共享父进程的虚拟内存空间调试
二、CLONE_FS | CLONE_FILEScode
线程作为父进程一个CPU执行单元,它能够直接使用父进程的文件系统信息(包括文件系统根目录,当前工做目录,和文件访问权限)而不须要本身独立建立和持有这些资源和父进程打开的文件描述符blog
三、CLONE_SIGHAND | CLONE_THREAD
线程还接着共享父进程的异步信号处理函数,即父进程能收到的异步信号,它也能收到并处理,不过线程能够自行经过sigprocmask来屏蔽或不屏蔽某些异步信号操做,而不影响其余线程。
四、CLONE_SYSVSEM
线程共享父进程的System V semaphore。
五、CLONE_SETTLS
线程支持TLS (Thread Local Storage)。TLS使得变量每个线程有一份独立实体,各个线程的值互不干扰
六、CLONE_PARENT_SETTID
父进程和线程会将线程ID保存在内核任务结构体的ptid成员。
七、CLONE_CHILD_CLEARTID
清除内核任务结构体的ctid成员上存储的线程ID。
八、CLONE_THREAD
将线程放入到父进程的线程组(thread group)里,这样线程在用户态就看不到本身进程ID了,只能看到父进程的进程ID,而且线程共享父进程的异步信号。
子进程则会复制父进程的不少进程信息,复制与共享的区别仍是很大的,复制须要从新申请内核资源,因此开销比线程大不少。
一、建立进程的时候没有指定 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,因此这些它须要复制进程的,因此父进程这些信息也被子进程继承过去了,可是已经独立存在在子进程里了,之后就没有关系了。
二、子进程还复制父进程的信号处理函数和信号;
三、线程和子进程都会继承(复制)父进程的普通优先级,父进程的栈,CPU状态会被设置为RUNNING。
总结一下:
一、进程,线程的建立还有不少细节,我这里没有彻底列举出来,只列举了我以为比较重要的部分。
二、线程,进程的最大区别就是,啃老仍是不啃老,经过上面来看,线程就是个啃老的货,而进程是个依靠父母独立成长的好孩子,然并卵,事实证实,不论是现实世界仍是计算机这个虚拟世界,越独立就越占用资源!
四、因此,多用线程能够下降系统资源的消耗。
上面讲到要多用线程,下面就轮到线程池要出场了,可是这个世界老是有不少熊孩子,就喜欢不停给他老子搞事情,严重到。。。咳咳,咱们仍是严肃的描述吧:
严重到不停的建立线程,销毁线程,这就增长了内核的开销了,更有甚之,有的线程函数无比简短,可能线程刚建立完就要销毁了。。。
因此避免出现这样的状况,不少场合须要用到进程池。
关于pthread能够参考以下文章:
http://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part1/
https://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part2/
https://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part3/
https://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part4/
https://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part5/