操作系统_并发_1.1_并发概念引入

一、线程的概念

1、关于线程

一般来讲,一个程序只有一个执行点(一个程序计数器,用来存放要执行的指令,不清楚的可以去看一看51单片机的取指令操作)。
而对于多线程来讲,程序会有多个执行点(多个程序计数器,每个都用于取指令和执行)。
从另一个角度来看,每个线程类似于独立的进程,但是区别在于,线程共享地址空间,从而可以访问相同的数据,因此单个线程的状态与进程状态非常相似。

关于线程的包含

  • 一个程序计数器PC,记录程序从哪里获取指令
  • 一组用于计算的寄存器
  • 一个线程切换到另一个线程,必然会发生的上下文切换

2、关于上下文切换

在多任务系统中,多个系统的同时运行,是CPU轮流分配产生的错觉,在实际的任务执行中,CPU需要知道任务从哪里加载,从哪里运行,此时也就用到了 CPU寄存器和程序计数器PC,CPU寄存器是内存,而程序计数器用来存储CPU正在执行的指令位置或者即将执行的下一条指令的位置,因此也被称为上下文。

2.2 进程上下文切换

2.2.1 概念

进程上下文切换,是指从一个进程切换到另一个进程运行。

2.2.2 进程切换只能发生在内核

由于进程是由内核来进行管理和调度的,进程的切换只能发生在内核态。

关于内核态的补充
对于操作系统而言,既有操作系统的程序,也有普通用户程序。
为了安全性和稳定性,要执行操作系统的程序,就必须切换到内核态。
而进程的管理,显然属于操作系统内核的管理范畴。
2.2.3 进程切换涉及的资源

进程的上下文切换包括虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。
因此,进程的上下文切换,在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

2.2.4 进程切换导致的CPU时间成本

根据测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间。这个时间还是相当可观的,特别是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。这也正是平均负载升高的一个重要因素。

2.2.5 进程切换的发生

显然,进程切换时才需要切换上下文.、

换句话说,只有在进程调度的时候,才需要切换上下文。一般来讲,操作系统为 CPU 都维护了一个就绪队列,将活跃进程(即正在运行和正在等待 CPU 的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待 CPU 时间最长的进程来运行。

那么,进程在什么时候才会被调度到 CPU 上运行呢?

最容易想到的一个时机,就是进程执行完终止了,它之前使用的 CPU 会释放出来,这个时候再从就绪队列里,拿一个新的进程过来运行。其实还有很多其他场景,也会触发进程调度,具体来看一下。

2.2.6 进程调度到 CPU 上

其一,时间片轮询,这也是一些芯片采用的轮询系统,例如南京沁恒的 TMOS
为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。

其二,进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。

其三,当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。

其四,当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。

最后一个,发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

2.3 线程上下文切换的两种情况

线程之间的上下文切换,类似于进程间的上下文切换,对于进程,我们需要将状态保存到进程控制块。所以我们需要一个或者多个进程控制块来保存进程的状态。
不过线程切换和进程切换相比的主要区别在于,地址空间不变。

  • 第一种
    前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。
  • 第二种
    前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。

二、创建线程

1、所用到的函数

//线程创建函数
pthread_create(pthread_t *th, const pthread_attr_t *attr, void *(* func)(void *), void *arg); 

//等待线程完成函数
pthread_join(pthread_t t, void **res);

//断言函数,用于在调试过程中捕捉程序错误
void assert (int expression);
/*
assert() 会对表达式expression进行检测:
如果expression的结果为 0(条件不成立),那么断言失败,表明程序出错,assert() 会向标准输出设备(一般是显示器)打印一条错误信息,并调用 abort() 函数终止程序的执行。
如果expression的结果为非 0(条件成立),那么断言成功,表明程序正确,assert() 不进行任何操作。

要打印的错误信息的格式和内容没有统一的规定,这和标准库的具体实现有关(也可以说和编译器有关),但是错误信息至少应该包含以下几个方面的信息:
断言失败的表达式,也即expression;
源文件名称;
断言失败的代码的行号。
*/

2、线程创建示例

我们在下面的程序的主函数中,使用pthread.h中的pthread_create函数创建了两个线程。分别执行mythread()函数,但是传入不同的参数。
关于此程序的编译:(GCC编译方式)
pthread库不是Linux系统默认的库,在window下编译也显示存在函数未定义的问题,所以在使用pthread_create创建线程,以及调用pthread_atfork()函数建立fork处理函数是,需要链接该库。
具体GCC可执行程序编译如下

gcc CreatThread.c -o thread -lpthread
//gcc .c文件名 -o 可执行程序文件名 链接thread库

可执行文件运行命令

windows下
./thread.exe
Linux下
./thread,out

具体程序代码

#include<stdio.h>
#include<assert.h>
#include<pthread.h>

void *mythread(void *arg){
    printf("%s\n",(char *)arg);
    return NULL;
}

int main(int argc,char *argv[]){
    pthread_t p1,p2;
    int rc;
    printf("main:begin\n");
    rc = pthread_create(&p1,NULL,mythread,"A"); 
    assert(rc == 0);
    rc = pthread_create(&p2,NULL,mythread,"B"); 
    assert(rc == 0);
    //join waits for the threads to finish
    rc = pthread_join(p1,NULL); 
    assert(rc == 0);
    rc = pthread_join(p2,NULL); 
    assert(rc == 0);
    printf("main:end\n");
    return 0;
}

3、线程运行现象

如果按照单进程程序的顺序执行逻辑,那么我们应该看到的运行现象是

开始运行
打印"main begin"
创建线程1
创建线程2
等待线程1
	运行线程1
	打印A
	返回
等待线程2
	运行线程2
	打印B
	返回
打印"main end"

但我们已经声明了,这种运行现象,适用于单进程程序的顺执逻辑,对于多线程真的适合么?
我们来看一下!

第一次运行,顺序改变发生在第20次

版权所有 (C) Microsoft Corporation。保留所有权利。

尝试新的跨平台 PowerShell https://aka.ms/pscore6

PS E:\MyGit\operation-system_-learning\example> ./thread.exe   	1
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe   	2
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	3
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	4
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	5
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	6
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	7
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	8
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	9
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	10
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	11
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	12
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	13
main:begin
A
B
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	14
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	15
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	16
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	17
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	18
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	19
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe	20
main:begin
B
A
main:end
PS E:\MyGit\operation-system_-learning\example> ./thread.exe

第二次,顺序改变发生在第11次

Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。

尝试新的跨平台 PowerShell https://aka.ms/pscore6
PS E:\MyGit\operation-system_-learning> cd .\example\
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	1
main:begin
A
main:end
PS E:\MyGit\operation-system_-learning\example> 
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	2
main:begin
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	3
main:begin
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	4
main:begin
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	5
main:begin
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	6
main:begin
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	7
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	8
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	9
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	10
main:begin
A
B
main:end
PS E:\MyGit\operation-system_-learning\example> .\thread.exe	11
main:begin
B
A
main:end

正如我们所见,线程创建有点像进行函数调用,但是并不是首先执行函数然后返回给调用者,而是为被调用的例程创建一个新的执行线程,它可以独立于调用者运行,可能在创建者返回之前运行,但也许会晚的多。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值