操作系统原理---操作系统中进程同步和互斥的概念

简介

进程同步是一个操作系统级别的概念,是在多道程序的环境下,存在着不同的制约关系,为了协调这种互相制约的关系,实现资源共享和进程协作,从而避免进程之间的冲突,引入了进程同步。

临界资源

在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。

对于临界资源的访问,必须是互诉进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。

对于临界区的访问过程分为四个部分:

1.进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞

2.临界区:在临界区做操作

3.退出区:清除临界区被占用的标志

4.剩余区:进程与临界区不相关部分的代码

进程间同步和互诉的概念

进程同步

进程同步也是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系来源于他们之间的合作。

比如说进程A需要从缓冲区读取进程B产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。概念如图1所示。

图1.进程之间的同步

用C#代码模拟进程之间的同步如代码1所示。

 class ProcessSyn
    {
        private static Mutex mut = new Mutex();

        static void Main()
        {
            Console.WriteLine("进程1执行完了进程2才能执行.......");
            Thread Thread1 = new Thread(new ThreadStart(Proc1));
            Thread Thread2 = new Thread(new ThreadStart(Proc2));
            Thread1.Start();
            Thread2.Start();
            Console.ReadKey();
        }

        private static void Proc1()
        {
            mut.WaitOne();
            Console.WriteLine("线程1执行操作....");
            Thread.Sleep(3000);
            mut.ReleaseMutex();//V操作

        }
        private static void Proc2()
        {

            mut.WaitOne();//P操作
            Console.WriteLine("线程2执行操作....");
            mut.WaitOne();
        }
    }

代码1.C#模拟进程之间的同步

运行结果如图2所示。

图2.运行结果

进程互斥

进程互斥是进程之间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。

比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。概念如图3所示。

图3.进程之间的互斥

用C#模拟进程之间的互斥,这里我启动了5个线程,但同一时间内只有一个线程能对临界资源进行访问。如代码2所示。

class ProcessMutex
    {
        private static Mutex mut = new Mutex();
        private const int numThreads = 5;

        static void Main()
        {

            for (int i = 0; i <= numThreads; i++)
            {
                Thread myThread = new Thread(new ThreadStart(UseResource));
                myThread.Name = String.Format("线程{0}", i + 1);
                myThread.Start();
            }
            Console.ReadKey();

        }

        //同步
        private static void UseResource()
        {
            // 相当于P操作
            mut.WaitOne();

            /*下面代码是线程真正的工作*/
            Console.WriteLine("{0}已经进入临界区",
                Thread.CurrentThread.Name);
            Random r = new Random();
            int rNum = r.Next(2000);

            Console.WriteLine("{0}执行操作,执行时间为{1}ms", Thread.CurrentThread.Name,rNum);
            Thread.Sleep(rNum);

            Console.WriteLine("{0}已经离开临界区\r\n",
                Thread.CurrentThread.Name);
            /*线程工作结束*/

            // 相当于V操作
            mut.ReleaseMutex();
        }
        //互斥

    }

代码2.C#模拟进程之间的互斥

运行结果如图4所示。

图4.C#模拟进程互斥

实现临界区互斥的基本方法

硬件实现方法

通过硬件实现临界区最简单的办法就是关CPU的中断。从计算机原理我们知道,CPU进行进程切换是需要通过中断来进行。如果屏蔽了中断那么就可以保证当前进程顺利的将临界区代码执行完,从而实现了互斥。这个办法的步骤就是:屏蔽中断--执行临界区--开中断。但这样做并不好,这大大限制了处理器交替执行任务的能力。并且将关中断的权限交给用户代码,那么如果用户代码屏蔽了中断后不再开,那系统岂不是跪了?

还有硬件的指令实现方式,这个方式和接下来要说的信号量方式如出一辙。但是通过硬件来实现,这里就不细说了。

信号量实现方式

    这也是我们比较熟悉P V操作。通过设置一个表示资源个数的信号量S,通过对信号量S的P和V操作来实现进程的的互斥。

P和V操作分别来自荷兰语Passeren和Vrijgeven,分别表示占有和释放。P V操作是操作系统的原语,意味着具有原子性。

P操作首先减少信号量,表示有一个进程将占用或等待资源,然后检测S是否小于0,如果小于0则阻塞,如果大于0则占有资源进行执行。

V操作是和P操作相反的操作,首先增加信号量,表示占用或等待资源的进程减少了1个。然后检测S是否小于0,如果小于0则唤醒等待使用S资源的其它进程。

前面我们C#模拟进程的同步和互斥其实算是信号量进行实现的。

一些经典利用信号量实现同步的问题

生产者--消费者问题

问题描述:生产者-消费者问题是一个经典的进程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。本作业要求设计在同一个进程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来

这里生产者和消费者是既同步又互斥的关系,首先只有生产者生产了,消费着才能消费,这里是同步的关系。但他们对于临界区的访问又是互斥的关系。因此需要三个信号量empty和full用于同步缓冲区,而mut变量用于在访问缓冲区时是互斥的。

利用C#模拟生产者和消费者的关系如代码3所示。

    class ProducerAndCustomer
    {
        //临界区信号量
        private static Mutex mut = new Mutex();

        private static Semaphore empty = new Semaphore(5, 5);//空闲缓冲区
        private static Semaphore full = new Semaphore(0, 5);
        //生产者-消费者模拟
         static void Main()
         {
             Console.WriteLine("生产者消费者模拟......");
             for (int i = 1; i < 9; i++)
             {
                 Thread Thread1 = new Thread(new ThreadStart(Producer));
                 Thread Thread2 = new Thread(new ThreadStart(Customer));
                 Thread1.Name = String.Format("生产者线程{0}", i);
                 Thread2.Name = String.Format("消费者线程{0}", i);
                 Thread1.Start();
                 Thread2.Start();
             }
             Console.ReadKey();

         }

         private static void Producer()
         {
             Console.WriteLine("{0}已经启动",Thread.CurrentThread.Name);
             empty.WaitOne();//对empty进行P操作
             mut.WaitOne();//对mut进行P操作
             Console.WriteLine("{0}放入数据到临界区", Thread.CurrentThread.Name);
                 Thread.Sleep(1000);
             mut.ReleaseMutex();//对mut进行V操作
             full.Release();//对full进行V操作
         }
         private static void Customer()
         {
             Console.WriteLine("{0}已经启动", Thread.CurrentThread.Name);
             Thread.Sleep(12000);
             full.WaitOne();//对full进行P操作
             mut.WaitOne();//对mut进行P操作
             Console.WriteLine("{0}读取临界区", Thread.CurrentThread.Name);
             mut.ReleaseMutex();//对mut进行V操作
             empty.Release();//对empty进行V操作
         }
    }

代码3.使用C#模拟生产者和消费者的关系

运行结果如图5所示。

图5.生产者消费者C#模拟结果

读者--写者问题

    问题描述:

一个数据文件或记录,统称数据对象,可被多个进程共享,其中有些进程只要求读称为"读者",而另一些进程要求写或修改称为"写者"。

规定:允许多个读者同时读一个共享对象,但禁止读者、写者同时访问一个共享对象,也禁止多个写者访问一个共享对象,否则将违反Bernstein并发执行条件。

通过描述可以分析,这里的读者和写者是互斥的,而写者和写者也是互斥的,但读者之间并不互斥。

由此我们可以设置3个变量,一个用来统计读者的数量,另外两个分别用于对读者数量读写的互斥,读者和读者写者和写者的互斥。如代码4所示。

class ReaderAndWriter
    {
        private static Mutex mut = new Mutex();//用于保护读者数量的互斥信号量
        private static Mutex rw = new Mutex();//保证读者写者互斥的信号量

        static int count = 0;//读者数量

        static void Main()
        {
            Console.WriteLine("读者写者模拟......");
            for (int i = 1; i < 6; i++)
            {

                Thread Thread1 = new Thread(new ThreadStart(Reader));
                Thread1.Name = String.Format("读者线程{0}", i);
                Thread1.Start();

            }
            Thread Thread2 = new Thread(new ThreadStart(writer));
            Thread2.Name = String.Format("写者线程");
            Thread2.Start();
            Console.ReadKey();

        }

        private static void Reader()
        {

            mut.WaitOne();
            if (count == 0)
            {
                rw.WaitOne();
            }
            count++;
            mut.ReleaseMutex();

            Thread.Sleep(new Random().Next(2000));//读取耗时1S
            Console.WriteLine("读取完毕");

            mut.WaitOne();
            count--;
            mut.ReleaseMutex();
            if (count == 0)
            {
                rw.ReleaseMutex();
            }

        }
        private static void writer()
        {

            rw.WaitOne();
            Console.WriteLine("写入数据");
            rw.ReleaseMutex();

        }

代码4.C#模拟读者和写者问题

运行结果如图6所示。

    

图6.读者写者的运行结果

哲学家进餐问题

问题描述:

有五个哲学家,他们的生活方式是交替地进行思考和进餐。哲学家们公用一张圆桌,周围放有五把椅子,每人坐一把。在圆桌上有五个碗和五根筷子,当一个哲学家思考时,他不与其他人交谈,饥饿时便试图取用其左、右最靠近他的筷子,但他可能一根都拿不到。只有在他拿到两根筷子时,方能进餐,进餐完后,放下筷子又继续思考。

图7.哲学家进餐问题

根据问题描述,五个哲学家分别可以看作是五个进程。五只筷子分别看作是五个资源。只有当哲学家分别拥有左右的资源时,才得以进餐。如果不指定规则,当每个哲学家手中只拿了一只筷子时会造成死锁,从而五个哲学家都因为吃不到饭而饿死。因此我们的策略是让哲学家同时拿起两只筷子。因此我们需要对每个资源设置一个信号量,此外,还需要使得哲学家同时拿起两只筷子而设置一个互斥信号量,如代码5所示。

class philosopher
    {
        private static int[] chopstick=new int[5];//分别代表哲学家的5只筷子
        private static Mutex eat = new Mutex();//用于保证哲学家同时拿起两双筷子
        static void Main()
        {
            //初始设置所有筷子可用
            for (int k = 1; k <= 5; k++)
            {
                chopstick[k - 1] = 1;
            }
            //每个哲学家轮流进餐一次
            for(int i=1;i<=5;i++)
            {
                Thread Thread1 = new Thread(new ThreadStart(Philosophers));
                Thread1.Name = i.ToString();
                Thread1.Start();
            }
            Console.ReadKey();
        }
        private static void Philosophers()
        {

            //如果筷子不可用,则等待2秒
            while (chopstick[int.Parse(Thread.CurrentThread.Name)-1] !=1 || chopstick[(int.Parse(Thread.CurrentThread.Name))%4]!=1)
            {
                Console.WriteLine("哲学家{0}正在等待", Thread.CurrentThread.Name);
                Thread.Sleep(2000);
            }
            eat.WaitOne();
            //同时拿起两双筷子
            chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 0;
            chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 0;
            eat.ReleaseMutex();
            Thread.Sleep(1000);
            Console.WriteLine("哲学家{0}正在用餐...",Thread.CurrentThread.Name);
            //用餐完毕后放下筷子
            chopstick[int.Parse(Thread.CurrentThread.Name)-1] = 1;
            chopstick[(int.Parse(Thread.CurrentThread.Name)) % 4] = 1;
            Console.WriteLine("哲学家{0}用餐完毕,继续思考", Thread.CurrentThread.Name);
        }
    }

代码5.C#模拟哲学家用餐问题

运行结果如图7所示。

图8.哲学家问题运行结果

总结

本文介绍了进程的同步和互斥的概念,临界区的概念,以及实现进程同步互斥的方式,并解决了3种实现同步的经典问题,并给出了相应的C#模拟代码。操作系统对于进程的管理是是计算机编程的基础之一,因此掌握这个概念会使你的内功更上一层:-D

时间: 2024-10-09 12:19:31

操作系统原理---操作系统中进程同步和互斥的概念的相关文章

操作系统--进程同步和互斥的概念

简介 进程同步是一个操作系统级别的概念,是在多道程序的环境下,存在着不同的制约关系,为了协调这种互相制约的关系,实现资源共享和进程协作,从而避免进程之间的冲突,引入了进程同步. 临界资源 在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源).但对于某些资源来说,其在同一时间只能被一个进程所占用.这些一次只能被一个进程所占用的资源就是所谓的临界资源.典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量

漫谈怎样学习操作系统原理

本人学习计算机技术至今,对于怎样学习操作系统原理有一点自己的看法,如今写出来,希望对大家能有所助! 操作系统怎么学.首先要想操作系统是怎么来的,在没有操作系统的年代里,人们是怎么编程的. 这是首要的问 题.不知道有没有人看过INTEL官方的CPU文档,总是分为应用级编程.系统级编程.指令集这三块.当中系统级编 程这一块最复杂,我不说内容,就单从文件的大小来看,IA-64编程手冊的应用级编程卷是2MB,系统卷却有6MB.就 连IA-32编程手冊上的系统卷部分也远比应用卷的要多.当然这些内容我还没有

漫谈如何学习操作系统原理

本人学习计算机技术至今,对于如何学习操作系统原理有一点自己的看法,现在写出来,希望对大家能有所助! 操作系统怎么学,首先要想操作系统是怎么来的,在没有操作系统的年代里,人们是怎么编程的.这是首要的问 题.不知道有没有人看过INTEL官方的CPU文档,总是分为应用级编程.系统级编程.指令集这三块.其中系统级编 程这一块最复杂,我不说内容,就单从文件的大小来看,IA-64编程手册的应用级编程卷是2MB,系统卷却有6MB.就 连IA-32编程手册上的系统卷部分也远比应用卷的要多.当然这些内容我还没有看

操作系统原理2——OS结构

操作系统原理2——OS结构 计算机系统是由硬件系统和软件系统两部分组成, 操作系统是软件系统的一个组成部分,它是直接在硬件系统的基础上工作的,所以在研究操作系统之前,先必须对计算机系统的结构有一个基本的了解,本章就是讲述计算机系统结构的基本知识. 本章的考核 知识点 是: 1.计算机系统的层次结构 2.硬件环境 3.操作系统结构 学习本章要求:了解计算机系统的结构,有关硬件的I/O中断和存储结构,硬件的保护措施:有关操作系统的结构,操作系统提供的使用接口. 重点 是:硬件环境和操作系统的结构 一

操作系统概念学习笔记 12 进程同步(二)管程

操作系统概念学习笔记 12 进程同步(二) 管程 基本的.高级的同步构造,即管程(monitor)类型. 使用: 管程类型提供了一组由程序员定义的.在管程内互斥的操作.管程类型的表示包括一组变量的声明(这些变量的值定义了一个类型实例的状态)和对这些变量操作的子程序和函数的实现.管程的类型表示不能直接为各个进程所使用.因此,在管程内定义的子程序只能访问位于管程内那些局部声明的变量和形式参数.类似的,管程的局部变量能被局部子程序访问. 管程结构确保一次只有一个进程能在管程内活动.不需要显示的编写同步

操作系统原理(转)

操作系统原理   我们每天都同操作系统打交道,了解一些操作系统原理上的知识是绝对有必要的,它可以让你了解操作系统内部是怎么工作的,为什么会出现这样那样的问题,为我们解决这些问题提供思路. 本文完全是为普通电脑用户写的,省略了所有难以理解的算法.原理,没有太多细节上的东西,只是针对普通电脑用户可能感兴趣的问题给出了实现思想.写这些东 西只是我的一相情愿,就怕我还是写得深了些,初学者读不懂.如果这10K文字能让你对操作系统有更深的了解,那也就不枉了这几个汗流浃背的夏日午后了. Q:什么是中断?A:中

操作系统原理学习总结

1.操作系统本质上属于软件的范畴,是一种比较特殊的,系统级的应用软件,所以,我们就可以用应用软件开发的普遍原理来理解操作系统.操作系统从字符界面到图形界面的进化和发展过程,就是就是不断满足用户需求的过程,软件工程讲究用户需求,在这里表现的比较突出,虽然字符界面比较灵活和功能强大,但是其操作比较复杂:而图形界面对于普通人而言操作简单,不需要培训,符合大众的需求,而且比较人性化.这才是windows比较流行的原因.当然,图形界面的本质还是字符界面,专业人员用的还是字符界面. 2. 进程是指,程序的一

操作系统原理(二),进程、线程

Technorati 标签: 操作系统,原理 现代操作系统比如,Linux,Windows等,都是支持"多任务"的操作系统.所谓多任务,指的就是操作系统可以同时运行多个任务.也就是在同一台电脑上,可以同时上网.听歌.使用Word,在过去单核的CPU上都已经可以支持多任务,实现的方式是操作系统让各个任务轮流交替执行.,比如任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒,因为CPU执行速度非常块,我们感觉到所有任务都是并发处理. 到了多核CPU时代

马哥学习笔记三十二——计算机及操作系统原理

缓存方式: 直接映射 N路关联 缓存策略: write through:通写 write back:回写 进程类别: 交互式进程(IO密集型) 批处理进程(CPU密集型) 实时进程(Real-time) CPU: 时间片长,优先级低IO:时间片短,优先级高 Linux优先级:priority 实时优先级: 1-99,数字越小,优先级越低 静态优先级:100-139,数据越小,优先级越高 实时优先级比静态优先级高 nice值:调整静态优先级   -20,19:100,139   0:120 ps