【OS 学习笔记】什么是同步、互斥?

一、背景

最近正在复习OS,为了更好理解其原理,便通过写博客来加深理解。由于知识水平有限,可能存在一些疏漏和不恰当之处,希望大佬们批评指点。

二、同步机制

  1. 什么是同步和互斥?

同步Synchronization也称作制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作关系
同步适用对象:(两个或多个)数据库、文件、模块、线程之间用来保持数据内容一致性的机制。

互斥是指某一资源(临界资源,例如打印机、共享公共变量等)同时只允许一个访问者对其进行访问,具有唯一性和排它性

在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

异步:相对于同步来说,不进行顺序执行。常JS引擎之使用(单线程)。另外,回调函数是异步操作最基本的方法。
关于异步的介绍

  1. 同步的基本原则
    在看基本原则之前,首先明白,为什么需要同步和互斥?因为 为了提高CPU的利用率,便出现了多批道计算机,这样便可以进行并发(同一间隔,区别并行)操作。而并发操作则带来了如何去分配CPU已经其他资源(如内存,IO,设备等)。而就在处于CPU有限,请求资源过多就需要合理的调度,而为了防止数据不一致则带来了同步和互斥的问题。
  • 空闲让进 当CPU空闲了,就需要让进程进去使用CPU(让CPU忙起来=> 提供CPU利用率)
  • 忙则等待 当CPU有其他进程正在访问时,则需要进行等待,做到互斥,防止数据不一致。
  • 有限等待 当然了,也不能一直等待,这样便会出现饥饿死等现象,用户请求得不到响应。
  • 让权等待 把CPU让给更加需要的请求进程去使用。
  1. 用代码来理解一下同步与互斥

进程1

public class SyncTask1 implements Runnable{
    @Override
    public void run() {
        System.out.println(new Date()+"SyncTask1正在工作...");
        System.out.println(new Date()+"SyncTask1工作结束...");
        // V操作,表面通知另外一个进程可以进行操作了,保证了同步。顺序执行
        SyncDemo.flag = true;
    }
}

进程2

public class SyncTask2 implements Runnable{
    @Override
    public void run() {
        // SyncTask2,它执行的前置动作还未完成,就一直等待,P操作进行检测上个动作是否完成
        while(!SyncDemo.flag);
        System.out.println(new Date()+"SyncTask2开始工作...");
        System.out.println(new Date()+"SyncTask2结束工作...");
    }
}

以上创建了两个进程,为了保证数据的安全和一致性,我们需要让进程2在进程1之后访问,按照顺序执行,也就是进程1是前置条件,进程2必须在进程1完成之后接着进行,这样来保证同步性。接下来进行测试。(其实有点像观察者模式?)

public class SyncDemo {
    // 同步PV操作的判断检测flag
    //volatile
    static public boolean flag = false;
    public static void main(String[]args){
        // 为演示效果,还故意将SyncTask2放在前面进行启动
        new Thread(new SyncTask2()).start();
        new Thread(new SyncTask1()).start();
    }
}

互斥(强调的是不能同时访问临界资源)。那么就设置一个mutex锁来判断是否有进程正在访问临界资源。于是有了以下代码。

public class Thread1 implements Runnable{

    @Override
    public void run() {
        // 当其他进程正在访问时,等待 =>
        while (MutexDemo.resourceMutex==false);
        // P操作,进行访问临界资源前的加锁
        MutexDemo.resourceMutex = false;
        for(int i =0;i<10;i++){
            System.out.println("Thread1 第"+i+"次工作");
            Resources.dataPool.add("产品"+i);
            //Resources.IoResources.;
        }
        // 释放锁,V操作
        MutexDemo.resourceMutex = true;

        // V操作进行提醒其他进程操作,达到同步
    }
}


public class Thread2 implements Runnable{
    @Override
    public void run() {
        while (!MutexDemo.resourceMutex);
        MutexDemo.resourceMutex = false;
        for(int i =0;i<10;i++){
            System.out.println("Thread2 第"+i+"次工作");
            Resources.dataPool.remove("产品"+i);
            //Resources.IoResources.;
        }
        // 释放锁,V操作
        MutexDemo.resourceMutex = true;
    }
}

  1. 生产者消费者问题

分析问题:明确进程数量,以及进程之间的同步和互斥情况。当数据缓存区有空的时候,生产者就进行生产产品,直到无空缓冲区停止生产,当数据缓冲区有满的时候,消费者就进行消费直到无可消费。另外,缓冲区不能同时访问,因此需要加锁。

确定关系:
在这里插入图片描述
有分析问题可知,我们需要empty,full变量来分别表示空缓冲区量和满缓冲区量。以及一个信号量锁mutex来判断是否有进程正在访问临界资源。

整理问题写成代码:

public class Consumer implements Runnable{
    @Override
    public void run() {
        while(true){
            while(Main.full<=0||Main.mutex);

            Main.mutex = true;
            System.out.println(new Date()+"准备消费产品;空buffer区数量为"+Main.empty+";满buffer区数量为" +
                    ""+Main.full);
            Main.full--;
            Main.empty++;
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(new Date()+"消费产品结束;空buffer区数量为"+Main.empty+";满buffer区数量为" +
                    ""+Main.full);
            Main.mutex = false;

            try {
                Thread.sleep(2000+new Random().nextInt(2)*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class Producer implements Runnable{
    @Override
    public void run() {
        while(true){
            // 同步P操作
            while (Main.empty<=0||Main.mutex);// 当空的buffer区小于等于0时进行等待(忙则等待?),负数代表有进程正在等待(消费者消费过度)?
            // 互斥P操作,加锁
            Main.mutex=true;
            System.out.println(new Date()+"准备生产产品;空buffer区数量为"+Main.empty+";满buffer区数量为" +
                    ""+Main.full);
            Main.full++;// full加一,进行生产产品,导致buffer区满(一次性就生产满》)
            Main.empty--;
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(new Date()+"生产产品结束;空buffer区数量为"+Main.empty+";满buffer区数量为" +
                    ""+Main.full);
            // 释放锁(V操作)
            Main.mutex=false;
            //
            try {
                Thread.sleep(20+new Random().nextInt(2)*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class Main {
    // 信号量
    static private final int N = 10;
    // 互斥锁,false代表无进程访问临界区
    volatile static public boolean mutex=false;
    // 空的buffer区
    volatile static public int empty=N;
    // 满的buffer区
    volatile static public int full=0;

    public static void main(String[]args){
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }
}

补充说明:在未加volatile时,会出现和预期不一样的结果,因为设计到Java内存模型机制了。如果不设置volatile那么就不可见,于是在生产者改变mutex时,消费者是不可见的依然保持原有的false因此只会出现生产者的打印。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值