精华内容
下载资源
问答
  • C++多线程临界区

    2017-11-04 19:37:42
    vs2010C++多线程临界区小案例 简单明了,适合初学者,,,
  • 操作系统的实验课设,实现Dekker,Lamport,Peterson,Eisenberg进程互斥访问临界区算法,使用java语言完成,可以动态显示进程访问临界区时各个进程的状态
  • vc++中使用临界区CriticalSection的例子.zip
  • 多线程程序设计中,用于实现互斥管理。对Windows临界区,内核事件,互斥量,信号量四种方式进行对比介绍
  • 临界区进程互斥模拟

    2014-10-13 16:59:13
    文档加Java实现代码,实现临界区资源模拟
  • BCB6.0临界区多线程

    2017-09-01 22:41:26
    本多线程是使用的临界区:CRITICAL_SECTION 按下按钮,通知线程要写入一串字符(线程执行完需要2s以上),延时20ms后通知线程再写入一串字符,跟第一次写入造成“冲突”,延时20ms后,在两个写都没完成时再通知线程...
  • 如题,包括如何实现一个互斥锁互斥锁和临界区
  • 临界区的使用实例.临界区的使用实例.临界区的使用实例.临界区的使用实例.临界区的使用实例.临界区的使用实例.
  • 【STM32】FreeRTOS临界区

    千次阅读 2020-11-08 17:22:01
    任务级临界区代码保护03.中断级临界区代码保护04. 预留05. 预留06. 附录07. 参考 01. 概述 临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中...

    00. 目录

    01. 概述

    临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS在进入临界端代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS系统本身就有很多的临界段代码。这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。

    FreeRTOS与临界段代码保护有关的函数有4个:

    /**
     * task. h
     *
     * Macro to mark the start of a critical code region.  Preemptive context
     * switches cannot occur when in a critical region.
     *
     * NOTE: This may alter the stack (depending on the portable implementation)
     * so must be used with care!
     *
     * \defgroup taskENTER_CRITICAL taskENTER_CRITICAL
     * \ingroup SchedulerControl
     */
    #define taskENTER_CRITICAL()               portENTER_CRITICAL() //任务级
    #define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR() //中断级
    
    /**
     * task. h
     *
     * Macro to mark the end of a critical code region.  Preemptive context
     * switches cannot occur when in a critical region.
     *
     * NOTE: This may alter the stack (depending on the portable implementation)
     * so must be used with care!
     *
     * \defgroup taskEXIT_CRITICAL taskEXIT_CRITICAL
     * \ingroup SchedulerControl
     */
    #define taskEXIT_CRITICAL()                portEXIT_CRITICAL() //任务级
    #define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) //中断级
    

    02. 任务级临界区代码保护

    taskENTER_CRITICAL()和taskEXIT_CRITICAL()是任务级的临界区代码保护,一个是进入临界区,一个是退出临界区,这两个函数一定要成对的使用。

    /**
     * task. h
     *
     * Macro to mark the start of a critical code region.  Preemptive context
     * switches cannot occur when in a critical region.
     *
     * NOTE: This may alter the stack (depending on the portable implementation)
     * so must be used with care!
     *
     * \defgroup taskENTER_CRITICAL taskENTER_CRITICAL
     * \ingroup SchedulerControl
     */
    #define taskENTER_CRITICAL()               portENTER_CRITICAL() //任务级
    
    
    /**
     * task. h
     *
     * Macro to mark the end of a critical code region.  Preemptive context
     * switches cannot occur when in a critical region.
     *
     * NOTE: This may alter the stack (depending on the portable implementation)
     * so must be used with care!
     *
     * \defgroup taskEXIT_CRITICAL taskEXIT_CRITICAL
     * \ingroup SchedulerControl
     */
    #define taskEXIT_CRITICAL()                portEXIT_CRITICAL() //任务级
    

    portENTER_CRITICAL()和portEXIT_CRITICAL()也是宏定义,在portmacro.h中有定义。

        #define portENTER_CRITICAL()                      vPortEnterCritical()
        #define portEXIT_CRITICAL()                       vPortExitCritical()
    

    vPortEnterCritical()和vPortExitCritical()函数实现如下:

    void vPortEnterCritical( void )
    {
        portDISABLE_INTERRUPTS();
        uxCriticalNesting++;
    
        /* This is not the interrupt safe version of the enter critical function so
         * assert() if it is being called from an interrupt context.  Only API
         * functions that end in "FromISR" can be used in an interrupt.  Only assert if
         * the critical nesting count is 1 to protect against recursive calls if the
         * assert function also uses a critical section. */
        if( uxCriticalNesting == 1 )
        {
            configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
        }
    }
    
    void vPortExitCritical( void )
    {
        configASSERT( uxCriticalNesting );
        uxCriticalNesting--;
    
        if( uxCriticalNesting == 0 )
        {
            portENABLE_INTERRUPTS();
        }
    }
    

    03.中断级临界区代码保护

    taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()是中断级别临界区代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于预先设置的值。

    /**
     * task. h
     *
     * Macro to mark the start of a critical code region.  Preemptive context
     * switches cannot occur when in a critical region.
     *
     * NOTE: This may alter the stack (depending on the portable implementation)
     * so must be used with care!
     *
     * \defgroup taskENTER_CRITICAL taskENTER_CRITICAL
     * \ingroup SchedulerControl
     */
    #define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR() //中断级
    
    /**
     * task. h
     *
     * Macro to mark the end of a critical code region.  Preemptive context
     * switches cannot occur when in a critical region.
     *
     * NOTE: This may alter the stack (depending on the portable implementation)
     * so must be used with care!
     *
     * \defgroup taskEXIT_CRITICAL taskEXIT_CRITICAL
     * \ingroup SchedulerControl
     */
    #define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) //中断级
    

    在portmacro.h文件中有如下定义:

        #define portSET_INTERRUPT_MASK_FROM_ISR()         ulPortRaiseBASEPRI()
        #define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )    vPortSetBASEPRI( x )
    

    ulPortRaiseBASEPRI()和vPortSetBASEPRI()相关实现

        static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
        {
            __asm
            {
                /* Barrier instructions are not used as this function is only used to
                 * lower the BASEPRI value. */
    /* *INDENT-OFF* */
            msr basepri, ulBASEPRI
    /* *INDENT-ON* */
            }
        }
    
        static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
        {
            uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    
            __asm
            {
                /* Set BASEPRI to the max syscall priority to effect a critical
                 * section. */
    /* *INDENT-OFF* */
                mrs ulReturn, basepri
                msr basepri, ulNewBASEPRI
                dsb
                isb
    /* *INDENT-ON* */
            }
    
            return ulReturn;
        }
    

    中断级临界区代码应用示例
    在这里插入图片描述

    04. 预留

    05. 预留

    06. 附录

    6.1 【STM32】STM32系列教程汇总

    网址:【STM32】STM32系列教程汇总

    07. 参考

    《FreeRTOS Reference Manual》

    《Using the FreeRTOS Real Time Kernel -A Practical Guide》

    《The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors,3rd Edition》

    展开全文
  • 具体地,在由单个固定点控制的量子临界区域中,当偏离临界点时蝶形速度降低。 而在非关键区域,蝶形速度的行为取决于低温下的特定相。 此外,在全息Berezinskii-Kosterlitz-Thouless过渡中,不存在蝶形速度的普遍性...
  • Delphi线程同步(临界区、互斥、信号量).pdf
  • Linux下临界区检测算法研究,王传瑞,肖涛,在Linux运行环境下,为了保证实时任务的截止期,一种新的临界区检测调度算法被提出,该算法通过实时任务的可调度性判断,重新安排�
  • 目录进程管理(实现临界区互斥的方法)一. 访问临界资源二. 实现临界区互斥的方法1. 通过软件实现(1)单标志法(2)双标志法先检查(3)双标志法后检查(4)Peterson's Algorithm2. 硬件实现方法(1)中断屏蔽方法(2)硬件指令...

    进程管理(实现临界区互斥的方法)

    一. 访问临界资源

    对临界资源的访问分为四个部分:

    1. 进入区:检查是否可以进入临界区,若可以则设置正在访问临界区的标志(加锁),以阻止其他进程同时进入临界区
    2. 临界区:进程中访问临界资源的那段代码
    3. 退出区:解除正在访问临界资源的标志(解锁)
    4. 剩余区:处理代码的其余部分
    do{
    	enter section;		//进入区
    	critical section;	//临界区
    	exit section;		//退出区
    	remainder section;	//剩余区
    }while true;
    

    二. 实现临界区互斥的方法

    同步机制应当遵循的准则:

    1. 空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
    2. 忙则等待:当已经有进程进入临界区时,其他试图进入临界区的进程必须等待
    3. 有限等待:对请求访问临界区的进程,应保证其在有限的时间内进入临界区
    4. 让权等待:当进程不能进入临界区时,应立即释放处理机

    1. 通过软件实现

    软件方法相对复杂且容易出错,因而现在系统较少采用,目前常用的方法是通过硬件方法实现同步互斥操作

    (1)单标志法

    特征:(设置标志turn)

    1. 设置turn来标志当前允许运行的进程编号
    2. 每个进程访问完临界区后把临界区的使用权转交给另一个进程
    3. 若turn的初始值为0,则运行进程始终按照p0->p1->p0->p1->…的顺序进行,如果其中的某个进程不再进入临界区则另一个进程也将无法进入临界区. 违背了"空闲让进"的原则
    turn=0;
    /**
    P0进程
    **/
    while(turn!=0);		//进入区
    critical section;	//临界区
    turn = 1;			//退出区
    remainder section;	//剩余区
    
    /**
    P1进程
    **/
    while(turn!=1);
    //turn初始值为0,若先进去的是p1进程则,
    //while循环条件成立,处理机一直进行空循环,
    //直到分配给进程的的时间片结束
    critical section;
    turn = 0;
    remainder section;
    

    (2)双标志法先检查

    特征:(设置flag[]标志对方和自己)

    1. 设置标志flag[i]来标志各个进程进入临界区的意愿
    2. 刚开始时先把flag[i]中各个元素的值设置成false
    3. 如果flag[i]的值为FALSE则表示pi进程未进入临界区,如果值为TRUE,表示Pi进程进入临界区
    4. 优点:不用按照一定顺序交替进入临界区,可以连续使用
    5. 缺点:由于操作系统的并发性,如果pi和pj进程同时进入临界区,则可能按照①②③③的顺序执行,则导致两个进程同时进入了临界区,违背了"忙则等待"的原则
    /**
    	pi进程:
    	当pi进程想要访问临界资源时,首先判断flag[j]是否为TRUE:
    	1. 如果为TRUE,说明pj进程处于临界区(在访问临界资源),则pi进程进行空循环,
    直到处理机分配给pi的时间片结束发生进程切换,使得pi进程退出处理机
    	2. 如果为FALSE,则说明pj进程不在临界区中(没有在访问临界资源),则将flag[i]
    设置成TRUE,以阻止其他进程来访问临界区(上锁),由此进入区完成
    	
    **/
    while(flag[j]);//进入区
    flag[i] = TRUE;//进入区
    critical section;			//临界区
    flag[i] = false;			//退出区
    remainder section;			//剩余区
    
    /**
    	pj进程:
    **/
    while(flag[i]);		②
    flag[j] = TRUE;		③
    critical section;
    flag[j] = false;
    remainder section;
    
    

    由上可以看出软件方法都会导致进程等待进入临界区时由于空循环造成的浪费处理机时间的现象,违背了“让权等待”

    (3)双标志法后检查

    特征:

    1. 设置flag[]来标志各个进程访问临界区的意愿
    2. 先将自己的flag[]标志设置成TRUE,再检查对方进程的标志,若对方标志为TRUE,则进程等待,否则进入临界区
    3. 若pi和pj进程几乎同时进入临界区时,由于并发性,可能将自己的flag都设置成TRUE,导致两个进程都只能处于等待状态,从而导致"饥饿"现象,违背了有限等待的原则
    /**
    	pi进程:
    **/
    flag[i] = TRUE;				//进入区
    while(flag[j]);				//进入区
    critical section;			//临界区
    flag[i] = false;			//退出区
    remainder section;			//剩余区
    
    /**
    	pj进程:
    **/	
    flag[j] = TRUE;		
    while(flag[i]);	
    critical section;
    flag[j] = false;
    remainder section;
    

    (4)Peterson’s Algorithm

    特点:(三标志,除了flag[]标志意愿之外还有turn标志)

    1. 为了防止进程为进入临界区而出现的无限等待的情况,在双标志法后检查的基础上增添了turn标志表示意愿把进入临界区的权限让给另外一个进程
    /**
    	pi进程:
    	首先将自己的flag标志设置成True(加锁,加锁后如果pj想进入临界区则会进入空循环中),
    	将不允许进入的程序标志turn设置成j,如果出现了两个进程几乎同时想要访问临界区的情况时,
    	在pi和pj将自己的意愿都设置成true之后,由于turn的值唯一,所以优先让一个进程进入了临界区,
    	另一个进程则处于空循环等待前一个进程退出临界区,解决了"无限等待"的问题
    **/
    flag[i] = TRUE;				//进入区
    turn = j;					//进入区
    while(flag[j] && turn ==j);	//进入区
    critical section;			//临界区
    flag[i] = false;			//退出区
    remainder section;			//剩余区
    
    /**
    	pj进程:
    **/	
    flag[j] = TRUE;	
    turn = i;	
    while(flag[i] && turn ==i);	
    critical section;
    flag[j] = false;
    remainder section;
    

    2. 硬件实现方法

    也称低级方法或原方法

    (1)中断屏蔽方法

    1. 由于CPU只在发生中断时引起进程切换,因此屏蔽中断可保证当前进程让临界区代码顺利执行完
    2. 缺点:限制了处理机交替执行程序的能力,因此执行效率会明显降低,同时将关中断的权利交给用户是十分危险的,如果一个进程关中断之后不再打开则会导致系统终止

    典型模式:

    /**
    关中断
    临界区
    开中断
    **/
    

    (2)硬件指令方法

    i.TestAndSet指令
    1. 这是一条原子操作(执行改代码时不允许被中断)
    2. 读出指定标志之后把该标志设置成真
    /**
    	TestAndSet:
    	lock用于表示临界资源的两种状态:true表示正被占用,初值为false
    **/
    boolean TestAndSet(boolean *lock){
    	boolean old;
    	old = *lock;	//用old记录指定标志
    	*lock = true;	//读出指定标志之后把标志lock设置成真
    	return old;	
    }
    
    /**
    	实现进程访问临界区的代码:
    	在进入临界区之前先用TestAndSet来检查lock和修改lock;若lock为false
    	说明临界资源没有被占用,则可以进入临界区,并把临界资源的占用情况设置成true(给临界资源上锁)。
    	如果lock为true则说明临界资源正被占用,则进程只能进行空循环直到处理机分配的时间片结束,进程退出。
    **/
    while TestAndSet(&lock);	//进入区
    进程的其他代码段				//临界区
    lock = false;				//退出区
    进程的其他代码;				//剩余区				
    
    
    ii.Swap指令
    1. Swap指令用于交换两个字节的内容
    /**
    	Swap指令:
    **/
    Swap(boolean *a,boolean *b){
    	boolean temp;		
    	temp = *a;
    	*a = *b;
    	*b = temp; 
    }
    
    /**
    	进程访问临界区处理:
    	lock用于表示临界资源是否被占用,true表示被占用
    	设置key用于与lock交换信息。在进入临界区之前先用key交换信息,如果交换后key仍为true,
    	说明临界资源已被占用,则接下来持续进行检查key和交换的过程,直到时间片结束
    **/
    key = true;				//进入区
    while(key!= false)
    	Swap(&lock,&key);	//进入区
    进程的临界区代码段;		//临界区
    lock =false;			//退出区
    进程的其他代码;			//剩余区
    
    
    1. 硬件方法的优点:适用于任意数目的进程,不管是单处理机还是多处理机。同时也支持进程中有多个临界区,只要为每个临界区都设置一个boolean变量就行。
    2. 缺点:进程在等待进入临界区的时候,处理机只能处于一个空循环的状态,这样的状态浪费处理机资源,违背了“让权等待”,从进程中随机选择一个进程进入临界区,有的进程就可能一直都选不上,导致饥饿现象,违背”有限等待“。
    展开全文
  • 临界区和C++使用方式

    2020-03-15 10:07:59
    一.临界资源 临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源... 每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,...

    一.临界资源

            临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。

    二.临界区:

            每个进程中访问临界资源的那段代码称为临界区(criticalsection,每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。

    三、优缺点

    优点:效率高,与互斥和事件这些内核同步对象相比,临界区是用户态下的对象,即只能在同一进程中实现线程互斥。因无需在用户态和核心态之间切换,所以工作效率比较互斥来说要高很多。

    缺点:资源释放容易出问题,Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。

    临界区是一种轻量级的同步机制,与互斥和事件这些内核同步对象相比,临界区是用户态下的对象,
    即只能在同一进程中实现线程互斥。因无需在用户态和核心态之间切换,所以工作效率比较互斥来说要高很多。

    四、API介绍

    1、初始化对象
    InitializeCriticalSection


    2、尝试进入临界区,如果成功调用,则进入临界区,如果资源被占用,不会阻塞


    TryEnterCriticalSectionThis function attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
    A nonzero value indicates that the critical section is successfully entered or the current thread already owns the critical section. Zero (FALSE) indicates 
    that another thread already owns the critical section.
     

    3、EnterCriticalSection  进入临界资源,取到控制权之前会阻塞
     Before using a critical section, some thread of the process must call the InitializeCriticalSection to initialize the object.
     
     To enable mutually exclusive access to a shared resource, each thread calls the EnterCriticalSection function to request ownership of the critical section before executing any section of code that accesses the protected resource。
     EnterCriticalSection blocks until the thread can take ownership of the critical section. 


    4、离开临界资源 
     When it has finished executing the protected code, the thread uses the LeaveCriticalSection function
     to relinquish ownership, enabling another thread to become owner and access the protected resource. 
     The thread must call LeaveCriticalSection once for each time that it entered the critical section.


    5、删除临界对象
    Any thread of the process can use the DeleteCriticalSection function to release the system resources that were allocated when the critical section object was initialized. After this function has been called, the critical section object can no longer be used for synchronization.
    删除之后临界资源就无效了

    五、代码示范

    #include <iostream>
    #include <windows.h>
    #include <thread>
    using namespace std;
    CRITICAL_SECTION g_cs;
    
    int g_Count = 0;
    void func1()
    {
    	while(1)
    	{
    		EnterCriticalSection(&g_cs);
    		g_Count++;
    		cout <<"t1 g_Count = " << g_Count << endl;
    		Sleep(3000);
    		LeaveCriticalSection(&g_cs);
    	}
    }
    
    void func2()
    {
    	while (1)
    	{
    		EnterCriticalSection(&g_cs);
    		g_Count++;
    		cout << "t2 g_Count = " << g_Count << endl;
    
    		Sleep(2000);
    		LeaveCriticalSection(&g_cs);
    	}
    }
    int main()
    {
    	InitializeCriticalSection(&g_cs);
    	std::thread t1(func1); // t1 is not a thread
    	std::thread t2(func2); // t1 is not a thread
    	t1.join();
    	t2.join();
    
    	
    	cin.get();
    	DeleteCriticalSection(&g_cs);
    	return 0;
    }

    六、注意

    1. 临界区对象不是内核对象,因此不能继承,不能跨进程,也不能用waitfor什么的函数来限定时间等待。这个很好理解,你想想WaitFor要求传一个句柄,而临界区对象的类型都不是句柄,也不能用CloseHandle来关闭,怎么可能会能让WaitForXXX搞了。

    2. 临界区对象使用前必须初始化,不初始化会崩溃,这是我的亲历。

    3. 线程进入临界区之前会先自旋那么几次,所有自旋锁都失败了之后会创建一个内核对象然后等待这个内核从而进入到内核模式。

    4. Enter和Leave必须匹配,每Enter一次,就得Leave一次,这又是可恶的计数机制。参见下面的代码

    typedef struct _RTL_CRITICAL_SECTION {
        PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    
        //
        //  The following three fields control entering and exiting the critical
        //  section for the resource
        //
    
        LONG LockCount;
        LONG RecursionCount;
        HANDLE OwningThread;        // from the thread's ClientId->UniqueThread
        HANDLE LockSemaphore;
        ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
    } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

    这是临界区对象的定义,看见

    RecursionCount

    这个对象了吧,你觉得它能干点啥?同时在这里你还能看到一个信号量的内核对象,还有一个自旋数量。这些玩意印证了上面的话。如果你同一个线程Leave之前Enter了两次,必须调用两个Leave,不然这个临界区对象依然会阻塞别的线程。再不明白就去看我前面有关挂起线程的那个博文。

    5. 由于进入临界去是无限等待的,因此你有时间肯定希望有种方法能够查看一下临界区是否可用,不可用则希望线程立刻去做其它的事情。这时候,你就需要一个TryEnterCriticalSectionAPI,这玩意很好理解,你踹一脚临界区,如果能进去就进去,不能进去这个API立刻以False返回,你就可以安排线程去做其它的事情。注意:你一脚踹进去了之后完事了记得要离开(LeaveCriticalSection)。

    5. 由于进入临界去是无限等待的,因此你有时间肯定希望有种方法能够查看一下临界区是否可用,不可用则希望线程立刻去做其它的事情。这时候,你就需要一个TryEnterCriticalSectionAPI,这玩意很好理解,你踹一脚临界区,如果能进去就进去,不能进去这个API立刻以False返回,你就可以安排线程去做其它的事情。注意:你一脚踹进去了之后完事了记得要离开(LeaveCriticalSection)。

    6. 前面说了,临界区真正用内核对象挂起线程之前会自旋好几次,因此你看对象里就有一个自旋锁的计数。你可以改这个自旋锁的数量。当然我不是说让你直接修改对象的成员变量!你可以在初始化的时候指定自旋锁的数量,用这个API:InitializeCriticalSectionAndSpinCount。在这里小说一下临界区为什么会自旋。因为程序从用户态转到内核模式需要昂贵的开销(大概数百个CPU周期),很多情况下,A线程还没完成从用户态转到内核态的操作呢,B线程就已经释放资源了。于是临界区就先隔一段时间自旋一次,直到所有自旋次数都耗尽,就创建个内核对象然后挂起线程。但是,如果您的机器只有一个CPU,那么这个自旋次数就没用了,操作系统直接会无视它。原因如下:你自旋着呢,操作B线程释放不了资源,于是你还不如直接切入等待状态让B来释放资源。动态更改自旋数量请使用SetCriticalSectionSpinCount,别做直接更改对象成员变量的二事!

    7. 最后,初始化临界区和进入临界区的时候都有可能会遇到异常状况,比如初始化的时候需要申请一些内存来保存DEBUG的信息(参见上面代码的第一个成员变量),如果内存不够,初始化就崩了。进入临界区的时候可能需要新建一个内核对象,如果内存不够,也崩了。解决这个问题的方法有两种

    1. 结构化异常处理
    2. 初始化的时候使用InitializeCriticalSectionAndSpinCount。这个API有返回值,如果它申请DEBUG信息失败,返回FALSE,另外,刚才提到了这个API可以指定自旋锁的自旋次数。这个自旋次数的范围并非是用0到0xFFFF
      FFFF而是0--->0x00FF FFFF,因此你可以设定高位为1指示初始化的时候直接建立内核对象。如果建立失败,这个函数也会调用失败。当然了,一上来就搞一个内核对象似乎有点浪费内存,但是这样能够保证进入临界区不会失败!但是吧,你需要注意,设置高位来保证内核对象的创建只能在2000上玩。MSDN上有说明,不信你看:
    3. Windows  2000:  If the high-order bit is set, the function pre-allocates the event used by theEnterCriticalSection
      function. Pre-allocation guarantees that entering or leaving the critical section will not raise an exception in low memory conditions. Do not set this bit if you are creating a large number of critical section objects, because it consumes a significant amount
      of nonpaged pool. Note that this event is allocated on demand starting with Windows XP and the high-order bit is ignored.

    最后是一些实验:

    我们看看用InitializeCriticalSection初始化一个临界区对象后,这些成员变量(除去DEBUG外)都是什么样子。

    我们Enter一下,看看会变成什么样子

    我们再让其它线程也Enter一下看看

    可见,新建了一个内核对象。

    我们现在让主线程退出临界区

    对照线程句柄我们可以看出第二个线程获得了临界区对象。

    我们再让第二个线程退出临界区。

    临界区除去内核对象外回到了原始状态。

     

    实验2:我们让临界区对象在同一线程内相继被进入两次

     

    ::EnterCriticalSection(&g_cs);
    ::EnterCriticalSection(&g_cs);

    可见,计数增加了一个,变成了,因此你得leave两次才能开锁

     

     

    RTL_CRITICAL_SECTION 结构。为方便起见,将此结构列出如下:

    struct RTL_CRITICAL_SECTION
    {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;
    HANDLE LockSemaphore;
    ULONG_PTR SpinCount;
    };
    
    

    以下各段对每个字段进行说明。

    DebugInfo 此字段包含一个指针,指向系统分配的伴随结构,该结构的类型为 
    RTL_CRITICAL_SECTION_DEBUG。这一结构中包含更多极有价值的信息,也定义于 WINNT.H 中。我们稍后将对其进行更深入地研究。

    LockCount 这是临界区中最重要的一个字段。它被初始化为数值 -1;此数值小于-1 
    时,表示此临界区被占用。当其不等于 -1 时,OwningThread 字段(此字段被错误地定义于 WINNT.H 中 — 应当是 DWORD 而不是 
    HANDLE)包含了拥有此临界区的线程 ID。

    RecursionCount 
    此字段包含当前所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。

    OwningThread 此字段包含当前占用此临界区的线程的线程标识符。此线程 ID 与 
    GetCurrentThreadId 之类的 API 所返回的 ID 相同。

    LockSemaphore 
    此字段的命名不恰当,它实际上是一个自复位事件,而不是一个信号。它是一个内核对象句柄,用于通知操作系统:该临界区现在空闲。操作系统在一个线程第一次尝试获得该临界区,但被另一个已经拥有该临界区的线程所阻止时,自动创建这样一个句柄。应当调用 
    DeleteCriticalSection(它将发出一个调用该事件的 CloseHandle 调用,并在必要时释放该调试结构),否则将会发生资源泄漏。

     

    SpinCount 旋转次数。仅用于多处理器系统。MSDN 
    文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount 
    次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用 
    InitializeCriticalSectionAndSpinCount API 将其设置为一个不同值。

     

    InitializeCriticalSectionAndSpinCount作用

    The InitializeCriticalSectionAndSpinCount function initializes a critical section object and sets the spin count for the critical section.

    BOOL InitializeCriticalSectionAndSpinCount(
      LPCRITICAL_SECTION lpCriticalSection,
                          // pointer to critical section
      DWORD dwSpinCount   // spin count for critical section
    );SetCriticalSectionSpinCountThe SetCriticalSectionSpinCount function sets the spin count for the specified critical section. DWORD SetCriticalSectionSpinCount(
      LPCRITICAL_SECTION lpCriticalSection, 
                          // pointer to critical section
      DWORD dwSpinCount   // spin count for critical section
    );

    当线程试图进入另一个线程拥有的关键代码段时,调用线程就立即被置于等待状态。这意
    味着该线程必须从用户方式转入内核方式(大约1 0 0 0个C P U周期)。这种转换是要付出很大代价的。

    因此, InitializeCriticalSectionAndSpinCount 的作用不同于InitializeCriticalSection 之处就在于设置了一个循环锁,不至于使线程立刻被置于等待状态而耗费大量的CPU周期,而在dwSpinCount后才转为内核方式进入等待状态。通常dwSpinCount设为4000较为合适 。 

    实际上对 CRITICAL_SECTION 的操作非常轻量,为什么还要加上旋转锁的动作呢?其实这个函数在单cpu的电脑上是不起作用的,只有当电脑上存在不止一个cpu,或者一个cpu但多核的时候,才管用。

    如果临界区用来保护的操作耗时非常短暂,比如就是保护一个reference counter,或者某一个flag,那么几个时钟周期以后就会离开临界区。可是当这个thread还没有离开临界区之前,另外一个thread试图进入 此临界区——这种情况只会发生在多核或者smp的系统上——发现无法进入,于是这个thread会进入睡眠,然后会发生一次上下文切换。我们知道context switch是一个比较耗时的操作,据说需要数千个时钟周期,那么其实我们只要再等多几个时钟周期就能够进入临界区,现在却多了数千个时钟周期的开销,真 是是可忍孰不可忍。

    所以就引入了InitializeCriticalSectionAndSpinCount函数,它的第一个参数是指向cs的指针,第二个参数 是旋转的次数。我的理解就是一个循环次数,比如说N,那么就是说此时EnterCriticalSection()函数会内部循环判断此临界区是否可以进 入,直到可以进入或者N次满。我们增加的开销是最多N次循环,我们可能获得的红利是数千个时钟周期。对于临界区内很短的操作来讲,这样做的好处是大大的。

    MSDN上说,他们对于堆管理器使用了N=4000的旋转锁,然后“This gives great performance and scalability in almost all worst-case scenarios.” 可见还是很有用的:-)

    8、多次调用 LeaveCriticalSection 导致死锁

    来自MSDN的忠告

    If a thread calls LeaveCriticalSection when it does not have ownership of the specified critical section object, an error occurs that may cause another thread using EnterCriticalSection to wait indefinitely.

    文章参考

    1、注意事项

    展开全文
  • 临界区管理

    2019-09-03 20:57:37
    临界区的调度原则 临界区与临界资源 并发进程中与共享变量有关的程序段成为临界区,共享变量代表的资源成为临界资源。 临界区调度原则 一次只允许一个进程进入临界区内执行 如果已有进程在临界区,其他视图进入的...

    临界区的调度原则

    临界区与临界资源

    并发进程中与共享变量有关的程序段成为临界区,共享变量代表的资源成为临界资源。

    临界区调度原则

    • 一次只允许一个进程进入临界区内执行
    • 如果已有进程在临界区,其他视图进入的进程等待
    • 进入临界区内的进程应在有限的时间内进出,一遍使等待进程中的一个进入

    实现临界区管理的几种错误算法

    • 两个进程都认为对方不在临界区中,同时进入了临界区
    • 两个进程都认为对方在临界区中,进而永久等待

    实现临界区管理的Peterson算法

    该算法为每个进程设置一个标志,当该标志为true时表示此进程要进入临界区另外设置一个指示器turn,一直是哪个进程可以进入临界区

    boolean flag[2];
    int turn;
    void procedure0()
    {
    while(true)
    {
    flag[0]=true;
    turn=1;
    while(flag[1]&&turn==1) /*若flag[1]为false,P0就进入临界区;若flag[1]为tureP0循环等待,只要P1退出临界区,P0即可进入*/
    {
    /* donothing*/
    }
    visit();/*访问临界区*/
    flag[0]=false;/*访问临界区完成,procedure0释放出临界区*/
    /*remainder section*/
    }
    
    }
    void procedure1()
    {
    while(true)
    {
    flag[1]=true;
    turn=0;
    while(flag[0]&&turn==0)
    {
    /* donothing*/ ;
    }
    visit();/*访问临界区*/
    flag[1]=false;/*访问临界区完成,procedure1释放出临界区*/
    /*remainder section*/
    }
    }
    void main()
    {
    flag[0]=flag[1]=false;
    /*start procedure0 and procedure1*/ ;
    }
    

    在上面的例子中,我们先假设turn被赋值为1,后赋值为0,那么线程0就该进入临界区,线程1则因为flag[0]&&turn==0而等待,当线程0访问临界区结束后释放了临界区资源–flag[0]=false;,那么此时flag[0]&&turn==0不成立,线程1因此可以运行临界区

    实现临界区管理的硬件设施

    关中断

    • 进程在进入临界区之前关中断,退出临界区时开中断,关中断期间,进程调度程序失去终端机或的机会,不会切换线程,保证了临界区的互斥执行
    • 关中断缺点:限制交叉执行程序的能力,关中断方法不适合多CPU系统,关中断权利赋予给用户十分危险

    测试并建立指令

    TS(X){
    若x=true,则x=false;
    return true;
    否则 return false;
    }
    
    

    用TS指令实现临界区管理(互斥)的算法如下

    bool TS(bool &x){
    	if(x){
    	x=false;
    	return true;
    	}
    }
    

    利用TS指令实现进程会吃的算法如下

    bool s=true;
    cobegin
    	process Pi(){
    	//i=1,2,3...n;
    	while(!TS(s))
    	s=true;
    	}
    

    对换指令

    void SWAP(bool &a,bool &b){
    bool temp=a;
    a=b;
    b=temp;
    }
    
    展开全文
  • 内核程序临界区和普通临界区

    千次阅读 多人点赞 2020-12-20 09:25:54
    临界区是进程访问临界资源的那段代码。按照我的理解,临界资源有很多种,所以一个进程有可能会有很多个临界区,分别用来访问不同的临界资源。 这些临界区中,有的是用来访问操作系统内核中的数据结构或数据的,比如...
  • 临界区

    2017-04-21 15:19:03
    临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。 比如,在一个办公室里有一台打印机。打印机...
  • 临界区的互斥

    千次阅读 2019-05-21 01:21:43
    访问共享的内存是临界区和其他代码相区别的地方,当计算机中运行的多个进程都有执行临界区的代码的时候,这个时候就会出现对共享内存的竞争。如果多个临界区都是对共享内存进行读,则问题不大,不会出现竞争;当某个...
  • 1.临界资源 临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。...每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许..
  • C++临界区

    千次阅读 2019-06-12 14:24:52
    原文链接:... 一、Win32平台 1、相关头文件和接口 #include <windows.h> ...CRITICAL_SECTION cs;//定义临界区对象 InitializeCriticalSection(&cs);//初始化临界区 EnterCri...
  • 每个进程中访问临界资源的那段代码称为临界区(Critical Section) (临界资源是一次仅允许一个进程使用的共享资源)。 每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界...
  • 关于在类的构造函数和析构使用临界区函数导致的多线程死锁的一个经验之谈
  • 同步互斥问题背景同步互斥临界区的设计禁用硬件中断基于软件(同步)的解决方法小结更高级的抽象 背景 计算机系统里会有多个进程存在,这多个进程相互之间还会进行交互。交互会引起对共享资源的访问,如果对这些共享...
  • 互斥:保证竟态资源安全的最朴素的一个思路就是让临界区代码“互斥”,即同一时刻最多只能有一个线程进入临界区。 最朴素的互斥手段:在进入临界区之前,用if检查一个bool值,条件不满足就“忙等”。这叫“锁变量...
  • 写在前面: 本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知...基本临界区是指宏 taskENTER_CRITICAL()与 taskEXIT_CRITICAL(...
  • freeRtos学习笔(3)临界区管理

    千次阅读 2020-12-24 18:18:07
    freeRtos临界区管理 freeRtos临界区 代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码 的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开...
  • 临界区是因为
  • 任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,020
精华内容 35,608
关键字:

临界区