用户级线程

线程的切换

在看进程切换前,我们先来看线程的切换吧。
这一篇主要说的是用户级线程的切换。
因为 进程的切换=资源切换+指令执行序列切换。
将资源和指令序列分开看,如果只是从一个执行指令序列切换到另一个执行指令序列,那么这就是线程的切换。

线程保留了并发(一个cpu上交替的执行多个程序)的优点,避免了进程切换代价,不需要切资源(映射表),只是切执行指令序列。线程切换的实质就是映射表不变而PC指针变。

用户级线程的切换

一个网页浏览器
一个线程用来从服务器接收数据
一个线程用来显示文本

开始实现这个游览器…

void WebExplorer()
{
    char URL[]="http://cms.hit.edu.cn";
    char buffer[1000];
    pthread_create(...,GetData,URL,buffer);
    pthread_create(...,Show,buffer);
}

void GetData(char* URL,char *p){...};
void Show(char* p){...};

我们下载了一段时间的数据后,切出去执行另一个线程,显示文本后,切回来继续下载。

这里写图片描述

Yield与Create

pthread_create()让多个线程同时触发,yield()能完成线程的切换,使线程交替执行。

现在有两个执行序列,我们想要其中执行了一段时间后,跳到另一个去执行,之后又切回来。(线程的切换)
这里写图片描述

这里写图片描述

Yiled从100跳到300
这里写图片描述

//B中的Yield
void Yield()
{
   找到300;
   jmp 300;
}
//D中的Yield
void Yield()
{
   找到204;
   jmp204;
}

两个执行序列与一个栈…

从100开始执行,在A函数中遇到B函数的调用,B的返回地址即下一句指令的地址104压栈,又在B中遇到Yield()调用,Yield()的返回地址204压栈。在B中的Yield()jmp到300,执行C函数,在C中遇到D()调用,304压栈,执行D(),遇到D中的Yield()调用,Yield()的返回地址404压栈。

这里写图片描述

D中的Yield()执行后,跳到204

200:B()
{
   Yield();
   204:
}

204开始执行遇到”}”,会弹栈,栈顶元素404被弹出执行404,这里就出现问题了,我们刚才已经回到100那个线程了,现在却又切到了300那个线程。

所以两个序列一个栈是不行的。

一个栈到两个栈

每个序列对应一个栈,Yield切换先切栈。

这里写图片描述

//D中的Yield
void Yield()
{
   TCB2.esp=esp;
   esp=TCB1.esp;
   jmp 204;
}

现在两个栈的情况是:

这里写图片描述

执行D中的Yield,使栈完成了切换,esp=1000。但是遇到jmp 204,就跳到204,那么Yield()就永远不能返回了。

//D中的Yield
void Yield()
{
   TCB2.esp=esp;
   esp=TCB1.esp;
  //jmp 204;
}

把jmp 204去掉,此时栈已经完成了切换esp=1000,然后Yield执行遇到“}”,弹栈,此时弹栈弹出栈顶元素 204,执行204(“}”相当于ret),再ret,弹栈就是104了。

所以两个线程的样子:两个TCB、两个栈、切换的PC在栈中。

而ThreadCreate的核心就是申请一个栈,一个TCB(线程控制块),再将栈与TCB关联。

void ThreadCreate(A)
{
   TCB* tcb=malloc();
   *stack=malloc();
   *stack=A;//100 执行线程的起始地址
   tcb.esp=stack;//栈和TCB关联
}

综上

void ThreadCreate(func,arg1)
{
   申请栈;
   申请TCB;
   func等入栈;
   关联TCB与栈;
}
void GetData(char* URL,char *p)
{
   连接URL;
   下载;
   Yield();
   ...
}
void Yield()
{
   压入现场;
   esp放在当前TCB中;
   Next();//调度函数,对系统影响很大,可优先调度Show
   从下个TCB取出esp;
   弹栈切换线程;
}

用户级线程

因为这里的Yield,Create是用户程序,没有进入内核,所以说是用户级线程。
用户级线程对于内核是不可见的。
如果进程的某个线程进入内核并阻塞,对于内核来说,它是看不到那个进程里面有其他线程的,所以会直接切到别的进程去执行。此时用户级线程的并发性就没有什么用了。

例如

GetData()
{
  连接URL发起请求;
  等待网卡IO...
  进程阻塞
}
Show()
{
  显示文本和连接;
  ...
}

用户级线程只能在用户级切来切去,不进入内核。

这里写图片描述

### 用户线程与内核线程的概念及区别 #### 1. 概念定义 用户线程是由用户程序创建和管理的线程,它们在进程内部运行,操作系统内核无法感知其存在[^4]。这些线程通常通过高编程语言(如Java)或库实现,并共享进程的内存空间和资源。 内核线程则是由操作系统内核直接创建和管理的线程,每个线程都有独立的内核上下文信息[^4]。内核线程的调度由操作系统完成,可以实现更细粒度的控制。 #### 2. 工作原理 - **用户线程的工作原理** 用户线程的切换完全在用户态下进行,不需要进入内核态。当一个线程需要切换到另一个线程时,仅需保存当前线程的寄存器状态、堆栈信息等,并加载目标线程的状态即可[^2]。这种切换方式开销较小,但若一个线程因I/O操作阻塞,则整个进程会随之阻塞,因为操作系统无法感知线程的存在。 - **内核线程的工作原理** 内核线程的切换涉及从用户态到内核态的切换。例如,从线程A切换到线程B时,需要保存线程A的用户栈和内核栈信息,更新线程控制块(TCB),然后加载线程B的内核栈和用户栈信息[^5]。由于每次切换都需要进入内核态,因此开销较大,但能够实现真正的并行执行[^3]。 #### 3. 区别对比 | **方面** | **用户线程** | **内核线程** | |---------------------|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| | **定义与创建方式** | 由用户程序创建和管理,无需内核支持。 | 由操作系统内核创建和管理,依赖系统调用和API。 | | **内核感知** | 操作系统内核不可感知,线程阻塞会导致整个进程阻塞。 | 操作系统内核可感知,线程阻塞不会影响其他线程的运行。 | | **模式切换** | 不需要频繁进入内核态,切换开销小[^4]。 | 需要频繁进入内核态,切换开销大[^4]。 | | **CPU调度单位** | CPU调度以进程为单位,线程间切换由用户程序控制[^4]。 | CPU调度以线程为单位,线程间切换由操作系统控制。 | | **并行性** | 无法实现真正意义上的线程并行[^4]。 | 可以实现真正意义上的线程并行[^4]。 | | **适用场景** | 适用于对性能要求较高且线程间通信频繁的场景[^4]。 | 适用于多处理器环境或需要高并行性的场景。 | #### 示例代码:用户线程与内核线程的切换 以下是一个简单的伪代码示例,用于说明用户线程和内核线程的切换过程: ```python # 用户线程切换示例 def user_level_thread_switch(current_thread, target_thread): # 保存当前线程的状态 save_registers_and_stack(current_thread) # 加载目标线程的状态 load_registers_and_stack(target_thread) # 内核线程切换示例 def kernel_level_thread_switch(current_thread, target_thread): # 进入内核态 enter_kernel_mode() # 保存当前线程的内核栈和用户栈 save_kernel_and_user_stack(current_thread) # 更新线程控制块(TCB) update_tcb(current_thread, target_thread) # 加载目标线程的内核栈和用户栈 load_kernel_and_user_stack(target_thread) # 返回用户态 return_to_user_mode() ``` ### 结论 用户线程和内核线程各有优缺点。用户线程切换开销小,但无法实现真正的并行;内核线程可以实现并行执行,但切换开销较大。选择哪种线程模型取决于具体的应用场景和需求。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值