<
金马国际娱乐城<
澳门各大赌场可见,线程的创建与销毁都是通过管理线程来完成的, 于是管理线程就成了linuxthreads的一个性能瓶颈.
创建与销毁需要一次进程间通信, 一次上下文切换之后才能被管理线程执行, 并且多个请求会被管理线程串行地执行.
NPTL
到了linux 2.6, glibc中有了一种新的pthread线程库–NPTL(Native POSIX Threading Library).
NPTL实现了前面提到的POSIX的全部5点要求. 但是, 实际上, 与其说是NPTL实现了, 不如说是linux内核实现了.
在linux 2.6中, 内核有了线程组的概念,task_struct结构中增加了一个tgid(thread group id)字段.
如果这个task是一个”主线程”, 则它的tgid等于pid, 否则tgid等于进程的pid(即主线程的pid).
在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).
类似的XXid在task_struct中还有两个:task->signal->pgid保存进程组的打头进程的pid、task->signal->session保存会话打头进程的pid。通过这两个id来关联进程组和会话。
有了tgid, 内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程, 也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候, 线程就不要展现了).
而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid,而tast_struct中的pid则由gettid系统调用来返回.
在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。
为了应付”发送给进程的信号”和”发送给线程的信号”, task_struct里面维护了两套signal_pending,一套是线程组共享的, 一套是线程独有的.
通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.
当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中.
NGPT
说到这里, 也顺便提一下NGPT(Next Generation POSIX Threads).
上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体), 这种模型称为1:1模型(1个线程对应1个内核级线程);而NGPT则打算实现M:N模型(M个线程对应N个内核级线程),也就是说若干个线程可能是在同一个执行实体上实现的. 线程库需要在一个内核提供的执行实体上抽象出若干个执行实体, 并实现它们之间的调度. 这样被抽象出来的执行实体称为用户级线程.
大体上, 这可以通过为每个用户级线程分配一个栈, 然后通过longjmp的方式进行上下文切换. (百度一下”setjmp/longjmp”, 你就知道.)
但是实际上要处理的细节问题非常之多.
目前的NGPT好像并没有实现所有预期的功能, 并且暂时也不准备去实现.
用户级线程的切换显然要比内核级线程的切换快一些, 前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器, 进入然后退出内核态. (进程切换则还需要切换地址空间等.)
而用户级线程则不能享受多处理器, 因为多个用户级线程对应到一个内核级线程上, 一个内核级线程在同一时刻只能运行在一个处理器上.
不过, M:N的线程模型毕竟提供了这样一种手段, 可以让不需要并行执行的线程
金马国际娱乐城<
澳门各大赌场可见,线程的创建与销毁都是通过管理线程来完成的, 于是管理线程就成了linuxthreads的一个性能瓶颈.
创建与销毁需要一次进程间通信, 一次上下文切换之后才能被管理线程执行, 并且多个请求会被管理线程串行地执行.
NPTL
到了linux 2.6, glibc中有了一种新的pthread线程库–NPTL(Native POSIX Threading Library).
NPTL实现了前面提到的POSIX的全部5点要求. 但是, 实际上, 与其说是NPTL实现了, 不如说是linux内核实现了.
在linux 2.6中, 内核有了线程组的概念,task_struct结构中增加了一个tgid(thread group id)字段.
如果这个task是一个”主线程”, 则它的tgid等于pid, 否则tgid等于进程的pid(即主线程的pid).
在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).
类似的XXid在task_struct中还有两个:task->signal->pgid保存进程组的打头进程的pid、task->signal->session保存会话打头进程的pid。通过这两个id来关联进程组和会话。
有了tgid, 内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程, 也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候, 线程就不要展现了).
而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid,而tast_struct中的pid则由gettid系统调用来返回.
在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。
为了应付”发送给进程的信号”和”发送给线程的信号”, task_struct里面维护了两套signal_pending,一套是线程组共享的, 一套是线程独有的.
通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.
当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中.
NGPT
说到这里, 也顺便提一下NGPT(Next Generation POSIX Threads).
上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体), 这种模型称为1:1模型(1个线程对应1个内核级线程);而NGPT则打算实现M:N模型(M个线程对应N个内核级线程),也就是说若干个线程可能是在同一个执行实体上实现的. 线程库需要在一个内核提供的执行实体上抽象出若干个执行实体, 并实现它们之间的调度. 这样被抽象出来的执行实体称为用户级线程.
大体上, 这可以通过为每个用户级线程分配一个栈, 然后通过longjmp的方式进行上下文切换. (百度一下”setjmp/longjmp”, 你就知道.)
但是实际上要处理的细节问题非常之多.
目前的NGPT好像并没有实现所有预期的功能, 并且暂时也不准备去实现.
用户级线程的切换显然要比内核级线程的切换快一些, 前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器, 进入然后退出内核态. (进程切换则还需要切换地址空间等.)
而用户级线程则不能享受多处理器, 因为多个用户级线程对应到一个内核级线程上, 一个内核级线程在同一时刻只能运行在一个处理器上.
不过, M:N的线程模型毕竟提供了这样一种手段, 可以让不需要并行执行的线程