精华内容
下载资源
问答
  • 2022-04-25 17:52:50

    前言

    • 操作系统包含的知识面很宽,需要一点点积累
    • 操作系统管理各种资源,包括线程与调度

    竞争与协作

    • 多个任务由于【资源共享】需要合作,并发执行的任务之间因此产生了【相互制约】的关系,制约关系可以分为【竞争】与【协作】两类
    • 并发任务之间的竞争关系为【互斥】
    • 并发任务之间的协作关系为【同步】

    同步

    • 任务之间协调彼此的工作而控制自己的执行进度,相互合作、相互等待而产生的【制约关系】

    互斥

    • 任务之间【竞争】临界资源而禁止两个以上的任务同时进入临界区所发生的【制约关系】

    案例

    • 两个任务并发执行,一个任务要等待另一个任务发来的消息,或者建立某个条件后再向前执行,这种任务间的制约合作关系被称位【任务的同步】
    更多相关内容
  • Python写的多线程使用互斥锁解决资源竞争的问题的代码,可以直接运行,并且带中文注释,方便初学者学习和使用。
  • 实验目的:通过编写程序实现进程的同步和互斥,使学生学会分析分析进程(线程)竞争资源现象,学习通过信号量解决进程互斥的方法。 实验原理:利用信号量机制解决进程(线程)的基本方法。 实验仪器:计算机一台。 ...
  • 学习python多任务
  • 如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确, 程序示例: import threading import time num1 = 0 def demo1(a): global num1 for i in range(a): num1 += 1 print("在...

    如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确,
    程序示例:

    import threading
    import time
    num1 = 0
    def demo1(a):
        global num1
        for i in range(a):
            num1 += 1 
        print("在demo1中,num1的值为:",num1)
    def demo2(a):
        global num1
        for i in range(a):
            num1 += 1 
        print("在demo2中,num1的值为:",num1)
    def main():
        nums = 1000000
        t1 = threading.Thread(target = demo1, args=(nums,))
        t2 = threading.Thread(target = demo2, args=(nums,))
        t1.start()
        t2.start()
        # 主线程等待两个子线程执行完毕
        time.sleep(5)
        print("在main中,num1的值为:",num1)
    if __name__ == '__main__':
        main()
    
    

    程序执行结果:
    在这里插入图片描述
    理论上 num1经过两个线程自加nums = 1000000次,最后应该得到num1 =2000000,可以看出这并不是我们预料到的结果,出现了严重的偏差,两个线程互相对num1进行加一赋值操作造成结果混乱,这就是多线程的资源竞争问题。

    那么,如何解决资源竞争问题?
    当存在多个线程或者进程同时调用一个全局变量资源,并且会对它进行修改时,可以采用上锁这个方法,来解决资源竞争问题。采用全局变量锁,每当线程调用全局变量时,就将该资源上锁,不允许被调用,只有当调用结束后才打开锁,这里引入互斥锁,能够保证全局变量资源的安全。

    互斥锁的定义:当在请求之前,资源没有被上锁,那么请求并不会堵塞;如果在请求之前,是被锁上的, 那么请求就处于一个堵塞状态,只有当前得锁被打开之后,才能再次上锁。

    互斥锁格式:

    # 创建锁
    mutex = threading.Lock()
    
    # 锁定
    mutex.acquire()
    
    # 释放
    mutex.release()
    

    针对以上问题,咱们试着用互斥锁解决,只需要将num1 += 1 锁住保持原子性就可以了,最后提一点:锁住的代码越少越好,,改善程序如下:

    import threading
    import time
    num1 = 0
    
    
    def demo1(a):
        
        global num1
        for i in range(a):
            lock_1.acquire() # 上锁
            num1 += 1 # 锁住 保持原子性
            lock_1.release() # 开锁
        print("在demo1中,num1的值为:",num1)
    
    
    def demo2(a):
    
        global num1
        for i in range(a):
            lock_1.acquire() # 上锁
            num1 += 1 # 锁住 保持原子性
            lock_1.release() # 开锁
        print("在demo2中,num1的值为:",num1)
        # lock_1.release()  # 开锁
    
    # 创建一个互斥锁,,默认是不上锁的状态
    lock_1 = threading.Lock()
    
    
    def main():
        nums = 1000000
        t1 = threading.Thread(target = demo1, args=(nums,))
        t2 = threading.Thread(target = demo2, args=(nums,))
        t1.start()
        t2.start()
        # 主线程等待两个子线程执行完毕
        time.sleep(2)
        print("在main中,num1的值为:",num1)
    
    
    if __name__ == '__main__':
        main()
    
    
    
    

    执行结果:
    在这里插入图片描述
    可以看出,虽然有一个线程结果并不理想,但已经很接近了,用互斥锁改善了很多,实际上我们最后需要的就是main里的结果,只需要保证最后我们需要的结果是正确的就行了。

    互斥锁解决资源竞争的好处:互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
    互斥锁的优缺点:

    优点:确保了某段关键代码只能由一个线程从头到尾完整地执行。
    缺点:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁.

    展开全文
  • 竞争条件,临界区,忙等待的互斥

    千次阅读 2018-05-01 15:50:16
    一、竞争条件和临界区在同一个应用程序中运行多个线程本身并不会引起问题。当多个线程访问相同的资源时才会出现问题。比如多个线程访问同一块内存区域(变量、数组、或对象)、系统(数据库、 web 服务等)或文件。...

    一、竞争条件和临界区

    在同一个应用程序中运行多个线程本身并不会引起问题。当多个线程访问相同的资源时才会出现问题。比如多个线程访问同一块内存区域(变量、数组、或对象)、系统(数据库、 web 服务等)或文件。事实上,只有一个或多个线程改写这些资源时才会出现问题。多个线程只读取而不会改变这些相同的资源时是安全的。

    两个线程访问同一个资源而且与线程访问资源时的顺序有关的这样一种情形就叫竞争条件。 导致竞争条件发生的代码片段就叫临界区。在临界区中可以通过恰当的线程同步来消除竞争条件。

    当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞争条件。导致竞争条件发生的代码称作临界区。

    对共享内存进行访问的程序片段称作临界区。

    对于一个好的实现互斥的解决方案,需要满足以下4个条件:

    1) 任何两个进程不能同时处于其临界区。

    2) 不应对CPU的速度和数量做任何假设。

    3) 临界区外运行的进程不得阻塞其他进程。

    4) 不得使进程无限期等待进入临界区。

    二、忙等待的互斥

    竞争条件:两个或多个进程读取某些共享数据,最后的结果取决于进程运行的精确时序,成为竞争条件。

    互斥:当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。

    临界区:对共享内存进行访问的程序片段成为临界区。

    实现互斥,避免竞争条件的方法

    1 屏蔽中断

    在单处理器系统中,最简单的方法是使每个进程在刚刚进入临界区后立即屏蔽所有中断,并在就要离开之前再打开中断。屏蔽中断后,时钟中断也被屏蔽。CPU只有发生时钟中断或其他中断时才会进行进程切换,这样,在屏蔽中断之后CPU将不会被切换到其他进程。于是,一旦某个进程屏蔽中断之后,它就可以检查和修改共享内存,而不必担心其他进程介入。

    这个方案并不好,因为把屏蔽中断的权力交给用户进程是不明智的。设想一下,若一个进程屏蔽中断后不再打开中断,其结果将会如何?整个系统可能会因此终止。而且,如果系统是多处理器(有两个或可能更多的处理器),则屏蔽中断仅仅对执行disable指令的那个CPU有效。其他CPU仍将继续运行,并可以访问共享内存。

    另一方面,对内核来说,当它在更新变量或列表的几条指令期间将中断屏蔽是很方便的。当就绪进程队列之类的数据状态不一致时发生中断,则将导致竞争条件。所以结论是:屏蔽中断对于操作系统本身而言是一项很有用的技术,但对于用户进程则不是一种合适的通用互斥机制。

    2 锁变量

    设想有一个共享(锁)变量,其初始值为0。当一个进程想进入其临界区时,它首先测试这把锁。如果该锁的值为0,则该进程将其设置为1并进入临界区。若这把锁的值已经为1,则该进程将等待直到其值变为0。于是,0就表示临界区内没有进程,1表示已经有某个进程进入临界区。

    但是仍存在问题,当进程a把锁变量设为1之前恰好又有进程b进入临界区,临界区将有2个进程。(假设一个进程读出锁变量的值并发现它为0,而恰好在它将其值设置为1之前,另一个进程被调度运行,将该锁变量设置为1。当第一个进程再次能运行时,它同样也将该锁设置为1,则此时同时有两个进程进入临界区中

    可能读者会想,先读出锁变量,紧接着在改变其值之前再检查一遍它的值,这样便可以解决问题。但这实际上无济于事,如果第二个进程恰好在第一个进程完成第二次检查之后修改了锁变量的值,则同样还会发生竞争条件。

    3 严格轮换法 

    忙等待:连续测试一个变量直到某个值出现为止,称为忙等待。

    自旋锁:用于忙等待的锁成为自旋锁。

    原理:

    整型变量turn,初始值为0,用于记录轮到哪个进程进入临界区,并检查或更新共享内存。开始时,进程0检查turn,发现其值为0,于是进入临界区。进程1也发现其值为0,所以在一个等待循环中不停地测试turn,看其值何时变为1(忙等待)。由于这种方式浪费CPU时间,所以通常应该避免。

    while(true){//进程0
        while(turn!=0);  //循环
        critical_region();  
        turn=1;  
        noncritical_region();  
    }  
    /  
    while(true){//进程1 
        while(turn!=1);  //循环
        critical_region();  
        turn=0;  
        noncritical_region();  
    }  

     图2-23 临界区问题的一种解法(在两种情况下请注意分号终止了while语句):a) 进程0;b) 进程1

    整型变量turn初始为0,进程0进入临界区,进程1忙等待,直到进程0离开临界区,并将turn设为1,然后进程1进入临界区,当进程1也离开临界区时,又把turn设为0,接着进程0再次进入临界区,以此类推。由于这种方法需要两者交替进行,如果进程0退出临界区时,turn设为1,但是进程1一直在处理非临界区的工作,进程0只有一直忙等待,直到进程1将turn设为0。这说明,在一个进程比另一个进程慢很多的情况下,轮流进入临界区不是好方法。只有在有理由认为等待时间是非常短的情形下,才使用忙等待。

    这种情况违反了前面叙述的条件3:进程0被一个临界区之外的进程阻塞。再回到前面假脱机目录的问题,如果我们现在将临界区与读写假脱机目录相联系,则进程0有可能因为进程1在做其他事情而被禁止打印另一个文件。

    实际上,该方案要求两个进程严格地轮流进入它们的临界区,如假脱机文件等。任何一个进程都不可能在一轮中打印两个文件。尽管该算法的确避免了所有的竞争条件,但由于它违反了条件3,所以不能作为一个很好的备选方案。

    4、peterson解法

    看这个方案是如何工作的。一开始,没有任何进程处于临界区中,现在进程0调用enter_ region。它通过设置其数组元素和将turn置为0来标识它希望进入临界区。由于进程1并不想进入临界区,所以enter_region 很快便返回。如果进程1现在调用enter_region,进程1将在此处挂起直到 interested[0]变成FALSE,该事件只有在进程0调用 leave_region退出临界区时才会发生。

    开始时临界区中无进程,假设进程0想进入临界区,进程1不想进入临界区,则进程0调用enter_region()后直接进入临界区,现在进程1想进入临界区,它要在此处挂起并等到进程0调用leave_region()离开临界区后才能进入临界区。如果两个进程同时想进入临界区并调用enter_region(),都把自己的进程号存入turn,则后存入的进程号会覆盖前写入的turn,则前进入的进程会进入临界区。 后进入的进程忙等待直到前一个进程退出临界区。 假设进程1是后存入的,则turn为1。当两个进程都运行到while语句时,进程0将循环0次并进入临界区,而进程1则将不停地循环且不能进入临界区,直到进程0退出临界区为止。

     

    图2-24   完成互斥的Peterson解法

    5 TSL指令(测试并加锁)

    某些计算机中,特别是那些设计为多处理器的计算机,都有下面一条指令:

    
    
    1. TSL RX, LOCK 

    称为测试并加锁(Test and Set Lock),它将一个内存字lock读到寄存器RX中,然后在该内存地址上存一个非零值。读字和写字操作保证是不可分割的,即该指令结束之前其他处理器均不允许访问该内存字。执行TSL指令的CPU将锁住内存总线,以禁止其他CPU在本指令结束之前访问内存。

    锁住存储总线不同于屏蔽中断。屏蔽中断,然后在读内存字之后跟着写操作并不能阻止总线上的第二个处理器在读操作和写操作之间访问该内存字。事实上,在处理器1上屏蔽中断对处理器2根本没有任何影响。让处理器2远离内存直到处理器1完成的惟一方法就是锁住总线,这需要一个特殊的硬件设施(基本上,一根总线就可以确保总线由锁住它的处理器使用,而其他的处理器不能用)。

    为了使用TSL指令,要使用一个共享变量lock来协调对共享内存的访问。当lock 为0时,任何进程都可以使用TSL指令将其设置为1,并读写共享内存。当操作结束时,进程用一条普通的move指令将lock的值重新设置为0。

    这条指令如何防止两个进程同时进入临界区呢?解决方案如图2-25所示。假定(但很典型)存在如下共4条指令的汇编语言子程序。第一条指令将lock 原来的值复制到寄存器中并将lock 设置为1,随后这个原来的值与0相比较。如果它非零,则说明以前已被加锁,则程序将回到开始并再次测试。经过或长或短的一段时间后,该值将变为0(当前处于临界区中的进程退出临界区时),于是过程返回,此时已加锁。要清除这个锁非常简单,程序只需将0存入lock即可,不需要特殊的同步指令。

      

    图2-25   用TSL指令进入和离开临界区

    现在有一种很明确的解法了。进程在进入临界区之前先调用enter_region,这将导致忙等待,直到锁空闲为止,随后它获得该锁并返回。在进程从临界区返回时它调用 leave_region,这将把lock设置为0。与基于临界区问题的所有解法一样,进程必须在正确的时间调用enter_region和 leave_region,解法才能奏效。如果一个进程有欺诈行为,则互斥将会失败。

    一个可替代TSL的指令是XCHG,它原子性地交换了两个位置的内容,例如,一个寄存器与一个存储器字。代码如图2-26所示,而且就像可以看到的那样,它本质上与TSL的解决办法一样。所有的Intel x86 CPU在低层同步中使用XCHG指令。

    
    
    1. enter_region:  
    2. MOVE REGISTER,#1    | 在寄存器中放一个1  
    3. XCHG REGISTER,LOCK  | 交换寄存器与锁变量的内容  
    4. CMP REGISTER,#0 |锁是零吗?  
    5. JNE enter_region    | 若不是零,说明锁已被设置,因此循环  
    6. RET | 返回调用者,进入临界区  
    7.  
    8. leave_region:  
    9. MOVE LOCK,#0    | 在锁中存入0  
    10. RET | 返回调用者 

      

    图2-26   用XCHG指令进入和离开临界区

    一个可替代TSL指令的是XCHG指令,原子性的交换两个位置的内容。本质上和TSL解法一样。

    peterson,TSL或XCHG都是正确的,但是它们都有忙等待的缺点:

    1 浪费cpu时间

    2 优先级反转问题:例如H进程优先级高,L进程优先级低 ,假设L处于临界区中,H这时转到就绪态想进入临界区,H需要忙等待直到L退出临界区,但是H就绪时L不能调度,L由于不能调度无法退出临界区,所以H永远等待下去。 



    展开全文
  • mutex体现的是一种竞争,我离开了,通知你进来。 cond体现的是一种协作,我准备好了,通知你开始吧。 互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的...
  • 临界区,互斥竞争条件 公共资源 可以是公共内存、公共文件、公共硬件等,总之是被所有任务共享的一套资源。 临界区 程序要想使用某些资源,必然通过一些指令去访问这些资源,若多个任务都访问同一公共资源,那么各...

    临界区,互斥,竞争条件

    公共资源

    可以是公共内存、公共文件、公共硬件等,总之是被所有任务共享的一套资源。

    临界区

    程序要想使用某些资源,必然通过一些指令去访问这些资源,若多个任务都访问同一公共资源,那么各任务中访问公共资源的指令代码组成的区域就称为临界区。怕有同学看得不仔细,强调一下,临界区是指程序中那些访问公共资源的指令代码,即临界区是指令,并不是受访的静态公共资源

    互斥

    互斥也可称为排他, 是指某一时刻公共资源只能被 1 个任务独享, 即不允许多个任务同时出现在自己的临界区中。公共资源在任意时刻只能被一个任务访问,即只能有一个任务在自己的临界区中执行,其他任务想访问公共资源时,必须等待当前公共资源的访问者完全执行完他自己的临界区代码后(使用完资源后)再开始访问。

    竞争条件

    竞争条件是指多个任务以非互斥的方式同时进入临界区, 大家对公共资源的访问是以竞争的方式并行进行的,因此公共资源的最终状态依赖于这些任务的临界区中的微操作执行次序。

    非互斥条件下产生的错误行为

    当多个任务“同时”读写公共资源时,也就是多个任务“同时”执行它们各自临界区中的代码时,它们以混杂并行的方式访问同一资源, 因此后面任务会将前一任务的结果覆盖, 最终公共资源的结果取决于所有任务的执行时序。这里所说的“同时”也可以指多任务伪并行,总之是指一个任务在自己的临界区中读写公共资源,还没来得及出来(彻底执行完临界区所有代码),另一个任务也进入了它自己的临界区去访问同一资源。

    内容出自 郑钢 – 《操作系统真象还原》,详细了解具体内容还是得看原书,本人只是将部分内容摘录为学习笔记为用。

    展开全文
  • 10-使用互斥锁解决资源竞争的问题.py
  • #创建互斥锁,默认是不上锁的 mutex = threading.Lock() def main(): t1 = threading.Thread(target=test1, args=(10000,)) t2 = threading.Thread(target=test2, args=(10000,)) t1.start() t2.start() #...
  • 使用互斥锁解决线程资源竞争: 什么是线程? 可以简单理解为同一进程中有多个计数器,每个线程的执行时间不确定,而每个进程的时间片相等,线程是操作系统调度执行的最小单位. 线程的创建步骤: import ...
  • 竞争条件与互斥

    2018-04-03 22:30:05
    本文主要讲解多线程(进程)编程时出现竞争条件的解决方案及代码实现。 竞争条件 协同进程可能共享一些彼此都能够读写的公用存储区(例如打印机脱机系统),也可能是一些共享文件,当两个或多个进程读写某些...
  • 目录一、内核中的竞争状态和互斥1、一些概念2、解决竟态的方法3、自旋锁和信号量的使用要点二、中断的上下半部1、中断处理的注意点2、中断下半部2种解决方案详解3、tasklet使用实战4、workqueue实战演示5、中断上下...
  • 文章目录多线程间的资源竞争互斥互斥锁示例可重入锁与不可重入锁死锁 多线程间的资源竞争 以下列task1(),task2()两个函数为例,分别将对全局变量num加一重复一千万次循环(数据大一些,太小的话执行太快,达不到...
  • 1. 数据竞争问题:对共享资源的访问会出现杂乱无序的结果 void shared_print(string msg, int id) { cout << "From" << id << " : " << msg << endl;// cout是可竞争资源, 需要...
  • 互斥锁 但是进程之间都是运行在一个操作系统上,进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端, 是可以的,而共享带来的是竞争竞争带来的结果就是错乱 #并发运行,效率高,但...
  • 为了解决多线程环境中的资源竞争,我们需要互斥锁,即每个线程都需要在修改或读取共享数据之前锁定互斥锁,并且在修改数据之后,每个线程都应解锁互斥锁。 5.1 std::mutex 在C++11线程库中,互斥锁位于mutex头文件中
  • 多(线程)进程中如何避免竞争,进行互斥 首先先引进临界区的概念: 我们把对共享内存进行访问的内存片段称作临界区域或临界区(critical region)。 如何避免竞争,需要满足以下四个条件 任何两个进程(线程)不能...
  • 互斥

    2021-04-06 13:52:12
    Linux中提供一把互斥锁mutex(也称之为互斥量)。 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。 但...
  • 互斥锁, var mu sync.Mutex (只有一个释放之后,下一个才能进去)(sync.Mutex类型只有两个公开的指针方法:Lock、Unlock)c. 读写锁, var mu sync.RWMutex(读不锁,只有写才锁)d.原子锁,import "sync/atomic" ...
  • 互斥 互斥的解决方案

    2021-01-05 20:13:56
    互斥的作用:保护数据的完整性 进程间通信会引起死锁,(两个都等互相发消息过来),可能会有饥饿现象 互斥的要求: 一次只允许一个资源去使用临界起源 在非临界区终止了这个进程的执行,也不能影响其他进程 不允许...
  • [align=center][size=large]线程间的竞争关系与线程互斥[/size][/align] [size=large]1.线程间的竞争关系[/size] 同一个进程中的多个线程由系统调度而并发执行时,彼此之间没有直接联系,并不知道其他线程的存在...
  • 线程同步互斥机制

    2022-05-22 08:54:34
    线程安全、互斥量、条件变量、信号量
  • 互斥锁基础

    千次阅读 2022-02-09 14:25:43
    互斥锁并不难,只要了解它的函数就可以去简单使用了。 1.初始化锁 1.1动态初始化 对于每把互斥锁,都有一个互斥变量,它是一个特殊的数据类型pthread_mutex_t。 int pthread_mutex_init(pthread_mutex_t *mutex,...
  • 互斥锁只允许一个进程进入临界区,适合保护比较长的临界区,因为竞争互斥锁时进程可能睡眠和再次唤醒,代价很高。 尽管可以把二值信号量当作互斥锁使用,但是内核单独实现了互斥锁。互斥锁的定义如下: include/...
  • MySQL mutex互斥

    2022-03-19 12:23:59
    在事务机制中,锁机制是为了保证高并发,...比如:两个线程,两个用户会话同时执行一个查询,需要访问相同的资源(一个文件、一个缓冲区或一些数据)时,这两个线程相互竞争。因此,第一个获取互斥锁的查询会导致另一个查
  • Qt多线程互斥

    2020-09-03 09:41:28
    假设两个线程都要访问一个全局变量的临界资源,两个线程谁都不让谁,进行对资源的抢占,发生了竞争,致使读写数据会出现错误,严重的可能还会导致程序运行崩溃,出错的现象谁也说不准,比如以下代码的示例: ...
  • Python互斥

    2021-10-07 15:21:16
    (2、线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁 (3、互斥锁为资源引入一个状态: 锁定/非锁定 (4、某个线程要更改共享数据时,先将其锁定,此时资源的状态为‘锁定’,其他线程不...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 72,552
精华内容 29,020
关键字:

互斥竞争