我真的不明白用户级线程和内核级线程之间的区别。这就是我目前所知道的:
内核级主题
优点:
缺点:
用户级主题
优点:
缺点:
据我所知,到目前为止,我无法回答这个问题:
创建内核级线程时,进程的.text(代码区域)如何修改?
我的直觉说它不会改变,因为线程共享相同的地址空间,因此.text区域不会改变。我发现支持这个答案的另一个原因是.text区域是只读的。此外,所有其他区域将保持不变(.bss,堆等)。唯一会改变的是堆栈。
但我想确定这是正确答案。
注意:我主要谈谈linux内核上的线程(不知道很多关于windows线程的东西)
答案 0 :(得分:2)
这里可能会询问4种不同类型的线程。
fork()
系统调用。clone()
系统调用创建,但由内核管理以下答案假定帖子中提到的“用户级”线程是pthread变种而不是“green”变种。
编辑:我认为@Hristo是正确的,OP正在谈论“绿色”线程或者让两者混淆。
这个答案假设您正在讨论使用pthread_create()
创建的线程。还有"green" threads由用户空间调度,内核不知道。我会在这里留下我的答案给后人。
用户主题==绿色主题
所谓的“绿色线程”是定义线程,完全由VM处理而不涉及内核。
“用户级”内核对线程一无所知,因此最多该进程一次只使用一个处理器/核心。
右。
如果一个线程进行阻塞调用,则所有线程将被再次阻塞,因为内核不知道线程是否存在
没有。只要您使用正确的原语,阻塞调用将导致将线程放入等待队列并运行另一个绿色线程。
创建内核级线程时,进程的.text(代码区域)如何修改?
在绿线中,它们都使用相同的代码区域。
此外,所有其他区域将保持不变(.bss,堆等)。
是
用户主题== pthreads
使用pthread_create()
“用户线程”,它们与“内核”线程之间几乎没有区别。 linux内核使用clone()
系统调用将用户级线程实现为单独的内核进程。 clone()
创建一个新进程,使用与父进程相同的内存空间而不是fork()
,它使用父进程内存的 copy 创建一个新进程。
“用户级”内核对线程一无所知,因此最多该进程一次只使用一个处理器/核心。
不,这不正确。内核管理用户级线程,就像任何其他进程一样,有上下文切换和在多个CPU上调度各种用户级线程的能力等。
如果一个线程进行阻塞调用,则所有线程将被再次阻塞,因为内核不知道线程是否存在
不,一个线程可以阻塞,其他线程将继续运行。同样,这是在pthread样式线程下。
创建内核级线程时,进程的.text(代码区域)如何修改?
创建新线程时,它会继承其父级的内存分段表。文本/代码页(和其他只读页面)标记为只读,两个线程共享内存,用于线程的生命周期。所有读写页面也共享,直到通过新线程对这些部分进行写入,此时页面被复制到另一个位置(写入时复制)。
此外,所有其他区域将保持不变(.bss,堆等)。
不,堆页面以及未标记为只读的任何其他内容(包括您提及的堆栈)将在写入后立即复制。我不确定.bss部分,但由于它们是在运行时初始化的,我相信它们也是可读写的。 DATA是只读的。
答案 1 :(得分:2)
从用户级线程切换到内核级线程(也称为LWP或轻量级进程)时,程序文本会发生变化。原因在于两种线程以完全不同的方式实现/编码。
内核级线程只是单独的指令流,它们共享除堆栈和线程局部存储(TLS)之外的所有内容。它们大多被内核视为真正的进程,但它们是轻量级的,因为它们彼此共享大量资源。实际上,Linux中的单线程进程只是只有一个LWP和所有其他内核结构的进程,这使得它成为一个完整的进程。 LWP或多或少地像往常一样执行单独的Linux进程 - 您不需要做任何事情来使它们同时运行 - 它们都由内核调度程序完成。与成熟流程的不同之处在于LWP共享其大部分数据。它们还共享可执行文件的.text
部分,但实际上.text
部分在同一个可执行文件的所有实例之间共享,而不仅仅是在其LWP之间。
用户级线程提供类似于真实多线程的东西,但它实际上是一个分时解决方案,通常由显式切换机制驱动。这意味着当一个用户级线程正在执行时,所有其他线程都被置于保持状态,因为只有一个指令流在从代码中的一个点跳转到另一个点,同时保存/恢复某些CPU寄存器(最明显的是堆栈)指针)。用户级线程的一种形式是在libpth
(GNU可移植线程)中实现的协作式多任务处理。用户级线程为用户代码添加了特殊的机制,允许线程在被认为是同步模式的情况下运行。例如,libpth
要求每个线程调用特定的API函数以允许其他一些线程运行。定时器信号可用于抢占用户级线程,但如果进程当前卡在阻塞系统调用中,则通常无法进行此操作。这就是为什么用户级线程通常使用高级内容(如异步I / O,非阻塞操作等)编写的原因,与内核级线程完全不同,后者使得阻塞系统调用完全可以接受。就内核而言,这一切都发生在单个内核级线程中。
类似于用户级线程的最值得注意的示例是使用select()
或poll()
在单线程可执行文件中并行处理多个网络连接。 "线程"在这种情况下,状态是连接状态结构,然后用于调用特定通信函数,该函数处理轮询系统调用检测为活动的套接字上的数据。
将用户级线程转换为内核级线程通常需要更改源代码(例如,必须删除对切换API的调用),因此.text
部分也会发生变化。将内核级线程转换为用户级线程也需要更改源代码,但可能并不总是可行。您不能只是在调用pthread_create()
时插入实现用户级线程的函数的名称,尽管如果用户级线程实现使用类似于定时器信号的内容,可能可能线程不会调用特殊的切换API ...
两个线程实现之间的另一个值得注意的区别是,在协作线程中,很难有竞争条件或共享冲突(即两个线程读取 - 修改 - 一次写入相同的内存位置)仅仅因为控制在代码中的众所周知的点处传输,而在抢占的情况下,该过程可以在其间的任何地方中断(例如,可以说在合作情况下所有操作都是原子的)并且这种访问必须由关键部分或其他部分保护。同步原语。
为了完整起见,Windows还有自己的用户级线程实现,称为 fiber 。
答案 2 :(得分:0)
Linux中实际上不存在用户级线程。每当使用pthread_create创建新线程时,也会创建相应的内核级线程。使用strace
时,应该清楚的是,每当您创建新线程时都会调用clone()
系统调用。
在任何情况下,所有线程共享.text,.data和.bss部分。 他们唯一不分享的是他们的筹码。 (是的,是的,并且线程局部变量。让我们不要进入那个。)