操作系统中的同步和异步

操作系统中的同步和异步操作系统中同步、异步性概念首先我们从操作系统的发展中学习什么是异步性。在操作系统发展的初期阶段,CPU处理的是作业,而且是单道批处理。什么意思呢?就是一个作业从提交到结束,程序员都不能干预,此时整台计算机就为这一个作业服务(可想有多少资源被”浪费”),这样有一点好处就是整个程序是”封闭的”。这样的操作表明人和机器是没有交互的。那我们怎么实现人机交互呢?这个答案是中断。中断的引入,使得工作人员能…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

操作系统中同步、异步性概念

首先我们从操作系统的发展中学习什么是异步性。在操作系统发展的初期阶段,CPU处理的是作业,而且是单道批处理。什么意思呢?就是一个作业从提交到结束,程序员都不能干预,此时整台计算机就为这一个作业服务(可想有多少资源被”浪费”),这样有一点好处就是整个程序是”封闭的”。这样的操作表明人和机器是没有交互的。那我们怎么实现人机交互呢?这个答案是中断。中断的引入,使得工作人员能在程序运行出问题的时候也能做出相应的处理。那么在当前程序中断后,计算机总不能让CPU不做事吧,所以人们引入了新的概念——进程。当A进程不能继续执行的时候(可能是因为资源不足、竞争,或是等待I/O处理),A进程会阻塞,而B进程有足够的资源,这时操作系统便把CPU分配给B进程。当然,这里还涉及到了中断处理程序。当A进程让出CPU之前,中断处理程序要做的是保护现场,即A进程的相关参数。当A进程等待的事件完成了,便可以返回中断点重新开始工作。

简单介绍发展史有助于我们更深刻的理解异步性的概念(当时我就是这样一步一步把异步性、同步概念串起来的)。进程引入后,让CPU的吞吐量得到了提升(若是单道批处理,作业等待I/O,那么这个时候CPU也要等)。但带来的问题是程序的运行失去了封闭性异步性是指进程以不可预知的速度向前推进。在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系(一般是资源问题)。内存中的每个进程何时执行,何时暂停,以怎样的速度向前推进,程序总共需要多少时间才能完成等,都是不可预知的。例如,当正在执行的进程提出某种资源请求时,如打印机请求,而此时打印机正在为其他的进程打印。由于打印机是临界资源,因此正在执行的进程必须等待,并且要放弃处理机。直到打印机空闲,并再次把处理机分配给该进程时,该进程才能继续执行。由于资源等因素的限制,进程的执行通常都不是 一气呵成,而是以 停停走走 的方式运行。

试想以下两个简单的小程序是两个进程,其中i是公共资源。

#include<stdio.h>//程序A
int i = 1;
int main()
{
    i = i + 10;
    //中间包含若干与i无关的操作
    printf("Ai = %d", i);
    return 0;
}

#include<stdio.h>//程序B
int i = 1;
int main()
{
    i++;
    //中间包含若干与i无关的操作
    printf("Bi = %d", i);
    return 0;
}

Jetbrains全家桶1年46,售后保障稳定

由于A和B是并发执行的,并且推进速度是不可预知的,所以最终的结果有多种情况。以下只分析两种①:Ai = 11 Bi = 12 即:A先运行i = i + 10;并打印出来,B再运行i++。②:Ai = 12 Bi = 12 即:A先运行 i = i + 10此时并没有打印,而B运行了i++后,A和B分别将i的值打印出来。其他情况也可以这样分析。因为异步性的关系,我们得到的答案可能是错误的,或是我们不想要的。为了解决这个问题,就必须引入同步机制,使得程序能按照规则运行下去,从而得到我们想要的答案。

创建父子进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
	pid_t pid =fork();
	if( pid == 0 )   //子进程返回值为0
	{
		while(1)
		{
			printf("This is child\n");
			sleep(1);
		}
	}
	else
	{
		while(1)
		{
			printf("This is parent\n");
			sleep(1);
		}
	}
	return 0;
}

操作系统中的同步和异步

可以看到,父子进程之间打印的信息并没有固定的先后顺序。当父子进程同时去访问资源时,也不能确定获取资源的先后顺序。这就表明进程的异步性可能出现我们不想要的结果,或者说是错误的结果。解决上述问题的方法就是”同步”。

同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。例如:上面两个程序可以看成5+3*5中的加法程序和乘法程序。若先执行乘法程序再执行加法程序,则5+3*5=20。这个答案一定是对的吗?其实不然。如果我们想要的答案是40,就要先执行加法程序再执行乘法程序,(5+3)*5=40。我们通过加 () 改变了运算的先后顺序,使先乘除后加减变成了先加减后乘除,这就是一种同步机制。

当我们理解了异步、同步的概念后,可以简单了解一下互斥的概念。互斥亦称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。例如,在仅有一台打印机的系统中,有两个进程A和进程B,如果进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。为禁止两个进程同时进入临界区,同步机制应遵循以下准则:

空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。

忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待。

有限等待:对请求访问的进程,应保证能在有限时间内进入临界区。

让权等待:当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。

线程同步例子(使用互斥锁):

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

/*全局变量*/
int sum = 0;
/*互斥量 */
pthread_mutex_t mutex;
/*声明线程运行服务程序*/
void* pthread_function1 (void*);
void* pthread_function2 (void*);

int main (void)
{
    /*线程的标识符*/
    pthread_t pt_1 = 0;
    pthread_t pt_2 = 0;
    int ret = 0;
    /*互斥初始化*/
    pthread_mutex_init (&mutex, NULL);
    /*分别创建线程1、2*/
    ret = pthread_create( &pt_1,                  //线程标识符指针
                           NULL,                  //默认属性
                           pthread_function1,     //运行函数
                           NULL);                 //无参数
    if (ret != 0)
    {
        perror ("pthread_1_create");
    }

    ret = pthread_create( &pt_2,                  //线程标识符指针
                          NULL,                   //默认属性
                          pthread_function2,      //运行函数
                          NULL);                  //无参数
    if (ret != 0)
    {
        perror ("pthread_2_create");
    }
    /*等待线程1、2的结束*/
    pthread_join (pt_1, NULL);
    pthread_join (pt_2, NULL);

    printf ("main programme exit!\n");
    return 0;
}

/*线程1的服务程序*/
void* pthread_function1 (void*a)
{
    int i = 0;
    printf ("This is pthread_1!\n");
    for( i=0; i<3; i++ )
    {
        pthread_mutex_lock(&mutex); /*获取互斥锁*/
        /*临界资源*/
        sum++;
        printf ("Thread_1 add one to num:%d\n",sum);
        pthread_mutex_unlock(&mutex); /*释放互斥锁*/
        /*注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用*/
        sleep (1);
    }
    pthread_exit ( NULL );
}

/*线程2的服务程序*/
void* pthread_function2 (void*a)
{
    int i = 0;
    printf ("This is pthread_2!\n");
    for( i=0; i<5; i++ )
    {
        pthread_mutex_lock(&mutex); /*获取互斥锁*/
        /*临界资源*/
        sum++;
        printf ("Thread_2 add one to num:%d\n",sum);
        pthread_mutex_unlock(&mutex); /*释放互斥锁*/
        /*注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用*/
        sleep (1);
    }
    pthread_exit ( NULL );
}

Linux下编译时需要加 -lpthread

注意第一个字母是大写,windows C语言中单位是毫秒(ms)。
Sleep (500); 
就是到这里停半秒,然后继续向下执行。
包含在#include <windows.h>头文件

在Linux C语言中 sleep的单位是秒(s)
sleep(5);//停5秒
包含在 #include <unistd.h>头文件

操作系统中的同步和异步

由于程序先创建的是 Thread_1,所以 Thread_1 先加锁,即拥有使用公共资源的权限。Thread_1 在加锁后休眠2秒,此时 Thread_2 被阻塞。若不加锁,Thread_2 能直接对公共资源进行操作。当 Thread_1 的工作完成,它释放互斥锁资源,之后运行 Thread_2。同理,当 Thread_2 运行时,Thread_1 被阻塞,直至 Thread_2 完成工作并释放互斥锁资源。

经典进程同步问题:生产者-消费者问题

(1) 描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

(2) 分析:

① 关系分析。生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,他们也是同步关系。

② 整理思路。这里比较简单,只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步 PV 操作的位置。

③ 信号量设置。信号量 mutex 作为互斥信号量,它用于控制互斥访问缓冲池,互斥信号量初值为1;信号量 full 用于记录当前缓冲池中

“满”缓冲区数,初值为0。信号量 empty 用于记录当前缓冲池中”空”缓冲区数,初值为 n。生产者-消费者进程的伪代码如下:

semaphore mutex=1; //临界区互斥信号量
semaphore empty=n; //空闲缓冲区
semaphore full=0;  //缓冲区初始化为空
producer () {      //生产者进程
    while(1){
        produce an item in nextp;  //生产数据
        P(empty);  //获取空缓冲区单元
        P(mutex);  //进入临界区.
        add nextp to buffer;  //将数据放入缓冲区
        V(mutex);  //离开临界区,释放互斥信号量
        V(full);  //满缓冲区数加1
    }
}

consumer () {     //消费者进程
    while(1){
        P(full);  //获取满缓冲区单元
        P(mutex); // 进入临界区
        remove an item from buffer;  //从缓冲区中取出数据
        V (mutex);  //离开临界区,释放互斥信号量
        V (empty) ;  //空缓冲区数加1
        consume the item;  //消费数据
    }
}

该类问题要注意对缓冲区大小为 n 的处理,当缓冲区中有空时便可对 empty 变量执行 P 操作,一旦取走一个产品便要执行 V 操作以释放空闲区。对 empty 和 full 变量的 P 操作必须放在对 mutex 的P操作之前。如果生产者进程先执行 P(mutex),然后执行 P(empty),消费者执行 P(mutex),然后执行P(fall),这样可不可以?答案是否定的。设想生产者进程已经将缓冲区放满,消费者进程并没有取产品,即empty = 0,当下次仍然是生产者进程运行时,它先执行 P(mutex) 封锁信号量,再执行 P(empty) 时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行 P(mutex),然而由于生产者进程已经封锁 mutex 信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,陷入了无休止的等待。同理,如果消费者进程已经将缓冲区取空,即 full = 0,下次如果还是消费者先运行,也会出现类似的死锁。不过生产者释放信号量时,mutex、full 先释放哪一个无所谓,消费者先释放mutex 还是 empty 都可以。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/219610.html原文链接:https://javaforall.net

(0)
上一篇 2025年7月5日 下午4:15
下一篇 2025年7月5日 下午4:43


相关推荐

  • mysql自增主键清零_Mysql自增主键归零的方法

    mysql自增主键清零_Mysql自增主键归零的方法要让自增重新从 1 开始 那么希赛小编教大家用下面的方法吧 方法一 如果曾经的数据都不需要的话 可以直接清空所有数据 并将自增字段恢复从 1 开始计数方法二 dbcccheckide table name reseed new reseed value 当前值设置为 new reseed value 如果自创建表后没有将行插入该表 则在执行 DBCCCHECKIDE 后插入的第一行将使用 new re

    2026年3月17日
    2
  • pycharm向左缩进_vscode整体缩进

    pycharm向左缩进_vscode整体缩进整体右移(缩进):鼠标全选住要缩进的代码,按tab整体左移(反缩进):鼠标全选住要缩进的代码,按tab+shift

    2022年8月25日
    6
  • HTML上传文件

    HTML上传文件文件上传表单 formaction method post enctype multipart form data div inputtype file multiple multiple accept image name image inputtype file multiple multiple accept image div div inputtype submit value 上传 inputtype submit value 上传 div formaction

    2026年3月20日
    2
  • 计算机组成原理知识点

    计算机组成原理知识点计算机体系结构(ComputerArchitecture)主要研究硬件和软件功能的划分,确定硬件和软件的界面,哪部分功能由硬件系统来完成,哪部分功能由软件系统来完成。计算机组成原理(ComputerOrganization)是依据计算机体系结构,在确定且分配了硬件子系统的概念结构和功能特性的基础上,设计计算机各部件的具体组成,以及它们之间的连接关系,实现机器指令级的各种功能和特性,这点上说

    2022年6月1日
    39
  • 那些强悍的PHP一句话后门

    那些强悍的PHP一句话后门以一个学习的心态来对待PHP后门程序,很多PHP后门代码让我们看到程序员们是多么的用心良苦。强悍的PHP一句话后门这类后门让网站、服务器管理员很是头疼,经常要换着方法进行各种检测,而很多新出现的编写技术,用普通的检测方法是没法发现并处理的。今天我们细数一些有意思的PHP一句话木马。利用404页面隐藏PHP小马 PHP  1 2 3 4…

    2022年5月11日
    50
  • GT911单片机驱动程序[通俗易懂]

    GT911单片机驱动程序[通俗易懂]GT911手册及驱动程序(安卓、MTK).rar-嵌入式文档类资源-CSDN下载https://download.csdn.net/download/qasxc78563/15117948参考:STM32F103驱动GT911-DarkBright-博客园https://www.cnblogs.com/DarkBright/p/10730346.htmlGT911与主机接口共有6PIN,分别为:VDD、GND、SCL、SDA、INT、RESET。这里用P11做中断脚,P13做复位

    2022年6月29日
    28

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号