互斥锁 订阅
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 展开全文
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
信息
领    域
汇编语言
中文名
互斥锁
互斥锁示例
下面举例:在Posix Thread中定义有一套专门用于线程同步的mutex函数。1. 创建和销毁有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下: pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。pthread_mutex_destroy ()用于注销一个互斥锁,API定义如下: int pthread_mutex_destroy(pthread_mutex_t *mutex) 销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。2. 互斥锁属性互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。3.锁操作锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。int pthread_mutex_lock(pthread_mutex_t *mutex)int pthread_mutex_unlock(pthread_mutex_t *mutex)int pthread_mutex_trylock(pthread_mutex_t *mutex)pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。4. 其他POSIX 线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,如果线程在加锁后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。这个锁机制同时也不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。互斥锁属性使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。表 4–1列出了用来处理互斥锁属性的函数。表 4–1 互斥锁属性例程表 4–2中显示了在定义互斥范围时 Solaris 线程和 POSIX 线程之间的差异。表 4–2 互斥锁范围比较
收起全文
精华内容
下载资源
问答
  • 而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。...
  • Java互斥锁简单实例

    2020-09-03 13:27:19
    主要介绍了Java互斥锁,较为详细的分析了java互斥锁的概念与功能,并实例描述了java互斥锁的原理与使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
  • 主要为大家详细介绍了C#多线程中如何运用互斥锁Mutex,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 该例程是参考FreeRTOS官方说明自己编写的代码,内部包含MDK工程源码,希望能帮助到初学FreeRTOS的学子。
  • Redis 互斥锁 Ruby 中使用 Redis 的分布式互斥锁。 支持阻塞和非阻塞语义。 这个想法来自。 概要 在下面的例子中,一次只有一个线程/进程/服务器可以进入锁定块。 RedisMutex . with_lock ( :your_lock_name ) ...
  • C++ 互斥锁源码

    2017-09-01 11:33:02
    ConsoleApp_Mutex,C++互斥锁源码cpp,可在VC++6.0或VS下直接编译运行,演示结果,控制台程序,ConsoleApp_Mutex,C++互斥锁源码cpp,可在VC++6.0或VS下直接编译运行,演示结果,控制台程序,
  • 互斥锁与事件锁

    2018-02-26 10:00:55
    里面详细介绍了互斥锁与事件锁,内有DEMO,并介绍了事件锁的两种方式的对比
  • 主要介绍了Python实现的多线程同步与互斥锁功能,涉及Python多线程及锁机制相关操作技巧,需要的朋友可以参考下
  • 主要介绍了简单的JavaScript互斥锁的相关资料,需要的朋友可以参考下
  • 主要介绍了Java使用synchronized实现互斥锁功能,结合实例形式分析了Java使用synchronized互斥锁功能简单实现方法与操作技巧,需要的朋友可以参考下
  • 主要介绍了举例讲解Python中的死锁、可重入锁和互斥锁,尽管线程编程方面Python的GIL问题老生常谈...需要的朋友可以参考下
  • 信号量与互斥锁的示例代码,参考书籍《深入理解计算机系统》
  • linux和win32下通用的互斥锁Mutex封装,统一接口,可直接使用
  • 自旋锁和互斥锁区别

    2020-07-18 05:33:26
    线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的机制(lock)来对多个线程之间共 享的临界区(Critical Section)进行保护(另一种常用的同步机制是barrier)。
  • vxworks 下互斥锁

    2014-07-01 14:29:27
    讲述了vxworks 互斥锁的应用,由于资料较少,这还是比较难得的
  • 一、互斥锁 互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。 1. 初始化: 在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化: 对于静态分配的互斥量, 可以把它设置为...
  • 使用互斥锁和共享内存实现的非阻塞FIFO,另外代码中有包含信号量的实现。个人测试稳定,有一些注释,一起学习。如有问题,欢迎讨论。
  • 主要介绍了PHP程序中的文件锁、互斥锁、读写锁使用技巧解析,其中重点讲解了sync模块和pthreads模块中的使用实例,需要的朋友可以参考下
  • 互斥锁示例代码

    2015-06-14 22:40:50
    Linux系统编程——线程同步与互斥:互斥锁,相关教程链接如下: http://blog.csdn.net/tennysonsky/article/details/46494077
  • 主要介绍了Python的互斥锁与信号量详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了Python多线程操作之互斥锁、递归锁、信号量、事件,结合实例形式详细分析了Python多线程操作互斥锁、递归锁、信号量、事件相关概念、原理、用法与操作注意事项,需要的朋友可以参考下
  • 本篇文章是对Linux下一个简单的多线程互斥锁的例子进行了分析介绍,需要的朋友可以参考下
  • 线程与互斥锁的应用

    2015-01-04 17:00:43
    用两个线程,一个线程echo,一个线程cat来读取手机的imei
  • 针对多道程序开发过程中存在的互斥锁标准不统一、使用复杂、易造成死锁等不足,提出扩展C/C++标准语法,增加临界资源定义及操作关键字的方案,通过隐藏互斥锁的概念及实现从而降低开发复杂度、实现平台无关....
  • 主要介绍了ReentrantLock 互斥锁,在同一时间只能被一个线程所占有,在被持有后并未释放之前,其他线程若想获得该锁只能等待或放弃,需要的朋友可以参考下
  • Java多线程并发的程序中使用互斥锁有synchronized和ReentrantLock两种方式,这里我们来详解Java多线程编程中互斥锁ReentrantLock类的用法:
  • FreeRTOS互斥锁

    千次阅读 2019-03-09 23:06:35
    信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥...互斥锁和递归互斥锁互斥锁是用来保证共享数据操作的完整性,同时只能有一个任务访问共享数据。递归互斥...

    信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。

    信号量包括二值信号量、计数信号量、互斥信号量和递归互斥信号量。和普通队列比起来,信号量虽然没有队列项实体,但是信号量值等同于队列项个数。

     

     

    互斥锁和递归互斥锁:互斥锁是用来保证共享数据操作的完整性,同时只能有一个任务访问共享数据。递归互斥锁和普通互斥锁比起来,同一个任务可以多次获取递归互斥锁,在释放同等次数之后才能解锁。

     

     

    在分析互斥锁之前,先搞清楚两个概念,优先级反转和优先级继承

    优先级反转:互斥锁被低优先级的任务持有,高优先级任务等待解锁。这时中等优先级任务一直运行,这导致高优先级任务在等待低优先级任务,而低优先级任务无法执行。这种高优先级等待中优先级的情况,叫做优先级反转。

    优先级继承:为了解决优先级反转而提出优化机制,让低优先级任务临时继承高优先级任务的优先级。在低优先级释放互斥锁之后,还要恢复原来的优先级。

     

     

    先看一下任务TCB

    因为优先级继承机制,在互斥锁释放后需要恢复优先级,uxBasePriority在优先级继承期间被用来保存任务优先级

    uxMutexesHeld表示任务持有了多少个互斥锁

    /* 任务TCB */
    typedef struct tskTaskControlBlock
    {
        volatile StackType_t *pxTopOfStack;		/* 栈顶地址 */
    
        ......
    
        ListItem_t xStateListItem;			/* 状态列表项:运行、就绪、挂起、阻塞 */
        ListItem_t xEventListItem;			/* 事件列表项 */
        UBaseType_t uxPriority;			/* 优先级 */
        StackType_t *pxStack;			/* 栈指针 */
        char pcTaskName[configMAX_TASK_NAME_LEN];	/* 任务名 */
    
        ......
    
    #if (configUSE_MUTEXES == 1)
        UBaseType_t uxBasePriority;		        /* 任务基础优先级 */
        UBaseType_t uxMutexesHeld;		        /* 互斥锁持有数量 */
    #endif
    
        ......
    }tskTCB;
    typedef tskTCB TCB_t;

     

     

    创建互斥锁

     互斥锁实际上是调用了队列的创建函数,创建好之后调用prvInitialiseMutex来初始化一些互斥锁特有的变量

    #define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
    /* 创建互斥锁 */
    QueueHandle_t xQueueCreateMutex(const uint8_t ucQueueType)
    {
    	QueueHandle_t xNewQueue;
    	const UBaseType_t uxMutexLength = (UBaseType_t)1, uxMutexSize = (UBaseType_t)0;
    
    	/* 创建互斥锁队列 */
    	xNewQueue = xQueueGenericCreate(uxMutexLength, uxMutexSize, ucQueueType);
    	/* 初始化互斥锁队列 */
    	prvInitialiseMutex((Queue_t *)xNewQueue);
    
    	return xNewQueue;
    }

    prvInitialiseMutex函数,将互斥锁初始化为没有被任何任务持有,并且处于解锁状态(队列中有一个队列项)

    /* 初始化互斥锁 */
    static void prvInitialiseMutex(Queue_t *pxNewQueue)
    {
    	if(pxNewQueue != NULL)
    	{
    		/* 初始化互斥锁的持有者为空 */
    		pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
    		/* 初始化队列类型为互斥锁 */
    		pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
    		/* 初始化递归次数为0 */
    		pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;
    
    		traceCREATE_MUTEX(pxNewQueue);
    
    		/* 初始化互斥锁为解锁状态(向队列中发送一个队列项) */
    		(void)xQueueGenericSend(pxNewQueue, NULL, (TickType_t)0U, queueSEND_TO_BACK);
    	}
    	else
    	{
    		traceCREATE_MUTEX_FAILED();
    	}
    }

     

     

    获取互斥锁

    互斥锁也叫互斥信号量,获取互斥锁和获取信号量使用同一个函数

    和普通信号量不同的是:

    1.获取成功时,将互斥锁持有者设为当前任务,当前任务持有互斥锁数量加一

    2.因互斥锁被其它任务持有而阻塞时,为了防止优先级反转现象,进行优先级继承处理

    3.因互斥锁被其它任务持有而阻塞,超时之后,因为可能进行了优先级继承,剥夺原先继承的优先级(剥不剥夺看优先级是否继承自该任务)

    从源代码看,调用xTaskPriorityInherit函数来进行优先级继承;在超时之后,使用prvGetDisinheritPriorityAfterTimeout和vTaskPriorityDisinheritAfterTimeout函数来恢复优先级

    #define xSemaphoreTake(xSemaphore, xBlockTime) xQueueSemaphoreTake((xSemaphore), (xBlockTime))
    /* 获取信号量值 */
    BaseType_t xQueueSemaphoreTake(QueueHandle_t xQueue, TickType_t xTicksToWait)
    {
    	BaseType_t xEntryTimeSet = pdFALSE;
    	TimeOut_t xTimeOut;
    	Queue_t *const pxQueue = xQueue;
    
    #if (configUSE_MUTEXES == 1)
    	BaseType_t xInheritanceOccurred = pdFALSE;
    #endif
    
    	configASSERT((pxQueue));
    
    	configASSERT(pxQueue->uxItemSize == 0);
    
    	#if ((INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1))
    	{
    		configASSERT(!((xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0)));
    	}
    	#endif
    
    	for(;;)
    	{
    		/* 进入临界区 */
    		taskENTER_CRITICAL();
    		{
    			/* 信号量计数 */
    			const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
    
    			/* 信号量值大于0 */
    			if(uxSemaphoreCount > (UBaseType_t)0)
    			{
    				traceQUEUE_RECEIVE(pxQueue);
    
    				/* 信号量值减一 */
    				pxQueue->uxMessagesWaiting = uxSemaphoreCount - (UBaseType_t)1;
    
    				#if (configUSE_MUTEXES == 1)
    				{
    					/* 队列类型是互斥锁 */
    					if(pxQueue->uxQueueType == queueQUEUE_IS_MUTEX)
    					{
    						/* 互斥锁的持有者设为当前任务,当前任务持有锁的数量加一 */
    						pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    				}
    				#endif
    
    				/* 等待释放信号量而阻塞的任务列表不为空 */
    				if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToSend)) == pdFALSE)
    				{
    					/* 将任务从释放信号量而阻塞的任务列表中移除,任务优先级大于当前任务优先级 */
    					if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE)
    					{
    						/* 请求切换任务 */
    						queueYIELD_IF_USING_PREEMPTION();
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    
    				/* 退出临界区 */
    				taskEXIT_CRITICAL();
    				
    				/* 成功 */
    				return pdPASS;
    			}
    			/* 信号量值为0 */
    			else
    			{
    				/* 等待时间为0 */
    				if(xTicksToWait == (TickType_t)0)
    				{
    					#if (configUSE_MUTEXES == 1)
    					{
    						configASSERT(xInheritanceOccurred == pdFALSE);
    					}
    					#endif
    					
    					/* 退出临界区 */
    					taskEXIT_CRITICAL();
    					traceQUEUE_RECEIVE_FAILED(pxQueue);
    					
    					/* 返回队列为空错误 */
    					return errQUEUE_EMPTY;
    				}
    				/* 没有记录过当前节拍状态 */
    				else if(xEntryTimeSet == pdFALSE)
    				{
    					/* 记录当前节拍状态 */
    					vTaskInternalSetTimeOutState(&xTimeOut);
    					
    					/* 已经记录过当前节拍状态 */
    					xEntryTimeSet = pdTRUE;
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    			}
    		}
    		/* 退出临界区 */
    		taskEXIT_CRITICAL();
    
    		/* 挂起调度器 */
    		vTaskSuspendAll();
    		
    		/* 锁定队列 */
    		prvLockQueue(pxQueue);
    
    		/* 检查是否超时,没有超时 */
    		if(xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE)
    		{
    			/* 检查信号量值是否为0,为0 */
    			if(prvIsQueueEmpty(pxQueue) != pdFALSE)
    			{
    				traceBLOCKING_ON_QUEUE_RECEIVE(pxQueue);
    
    				#if (configUSE_MUTEXES == 1)
    				{
    					/* 队列类型为互斥锁 */
    					if(pxQueue->uxQueueType == queueQUEUE_IS_MUTEX)
    					{
    						/* 进入临界区 */
    						taskENTER_CRITICAL();
    						{
    							/* 任务优先级继承,返回值是否继承优先级 */
    							xInheritanceOccurred = xTaskPriorityInherit(pxQueue->u.xSemaphore.xMutexHolder);
    						}
    						/* 退出临界区 */
    						taskEXIT_CRITICAL();
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    				}
    				#endif
    
    				/* 将任务插入等待获取信号量而阻塞的任务列表中 */
    				vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToReceive), xTicksToWait);
    				/* 解锁队列 */
    				prvUnlockQueue(pxQueue);
    				/* 解除调度器挂起,没有切换过任务 */
    				if(xTaskResumeAll() == pdFALSE)
    				{
    					/* 请求切换任务 */
    					portYIELD_WITHIN_API();
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    			}
    			/* 队列不为空,while下一个循环时取走队列项 */
    			else
    			{
    				/* 解锁队列 */
    				prvUnlockQueue(pxQueue);
    				/* 解除调度器挂起 */
    				(void)xTaskResumeAll();
    			}
    		}
    		/* 已经超时或者超时之后 */
    		else
    		{
    			/* 解锁队列 */
    			prvUnlockQueue(pxQueue);
    			
    			/* 解除调度器挂起 */
    			(void)xTaskResumeAll();
    
    			/* 检查队列是否为空,为空 */
    			if(prvIsQueueEmpty(pxQueue) != pdFALSE)
    			{
    				#if (configUSE_MUTEXES == 1)
    				{
    					/* 继承了优先级 */
    					if(xInheritanceOccurred != pdFALSE)
    					{
    						/* 进入临界区 */
    						taskENTER_CRITICAL();
    						{
    							UBaseType_t uxHighestWaitingPriority;
    
    							/* 获取剩下所有等待互斥锁任务的最高优先级 */
    							uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout(pxQueue);
    							/* 超时之后剥夺继承优先级 */
    							vTaskPriorityDisinheritAfterTimeout(pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority);
    						}
    						/* 退出临界区 */
    						taskEXIT_CRITICAL();
    					}
    				}
    				#endif
    
    				traceQUEUE_RECEIVE_FAILED(pxQueue);
    				
    				/* 返回队列为空错误 */
    				return errQUEUE_EMPTY;
    			}
    			/* 队列不为空,while下一个循环时取走队列项 */
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    	}
    }

     

     

    释放互斥锁

    从源代码来看,看不出和普通信号量处理有什么区别。

    但是,因为互斥锁可能存在优先级继承的问题,因此释放互斥锁时肯定需要恢复优先级。

    事实上,恢复优先级在prvCopyDataToQueue函数中进行。

    #define xSemaphoreGive(xSemaphore) xQueueGenericSend((QueueHandle_t)(xSemaphore), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK)
    /* 发送队列项 */
    BaseType_t xQueueGenericSend(QueueHandle_t xQueue, const void *const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition)
    {
    	BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    	TimeOut_t xTimeOut;
    	Queue_t *const pxQueue = xQueue;
    
    	configASSERT(pxQueue);
    	configASSERT(!((pvItemToQueue == NULL) && (pxQueue->uxItemSize != (UBaseType_t)0U)));
    	configASSERT(!((xCopyPosition == queueOVERWRITE) && (pxQueue->uxLength != 1)));
    	
    	#if ((INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1))
    	{
    		configASSERT(!((xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0)));
    	}
    	#endif
    
    	for(;;)
    	{
    		/* 进入临界区 */
    		taskENTER_CRITICAL();
    		{
    			/* 目前已插入队列项数小于最大可插入队列数或者覆盖型插入 */
    			if((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE))
    			{
    				traceQUEUE_SEND(pxQueue);
    
    				#if (configUSE_QUEUE_SETS == 1)
    				{
    					UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;
    
    					xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);
    
    					if(pxQueue->pxQueueSetContainer != NULL)
    					{
    						if((xCopyPosition == queueOVERWRITE) && (uxPreviousMessagesWaiting != (UBaseType_t)0))
    						{
    							mtCOVERAGE_TEST_MARKER();
    						}
    						else if(prvNotifyQueueSetContainer(pxQueue, xCopyPosition) != pdFALSE)
    						{
    							queueYIELD_IF_USING_PREEMPTION();
    						}
    						else
    						{
    							mtCOVERAGE_TEST_MARKER();
    						}
    					}
    					else
    					{
    						if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE)
    						{
    							if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE)
    							{
    								queueYIELD_IF_USING_PREEMPTION();
    							}
    							else
    							{
    								mtCOVERAGE_TEST_MARKER();
    							}
    						}
    						else if(xYieldRequired != pdFALSE)
    						{
    							queueYIELD_IF_USING_PREEMPTION();
    						}
    						else
    						{
    							mtCOVERAGE_TEST_MARKER();
    						}
    					}
    				}
    				#else
    				{
    					/* 根据不同的入队方式,将队列项数据拷贝到队列中 */
    					xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);
    
    					/* 等待接收队列项而阻塞的任务列表不为空 */
    					if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE)
    					{
    						/* 将任务从事件列表中移除一个任务,并挂接到就绪列表 */
    						if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE)
    						{
    							/* 请求切换任务 */
    							queueYIELD_IF_USING_PREEMPTION();
    						}
    						else
    						{
    							mtCOVERAGE_TEST_MARKER();
    						}
    					}
    					/* 等待接收队列项而阻塞的任务列表为空 */
    					else if(xYieldRequired != pdFALSE)
    					{
    						queueYIELD_IF_USING_PREEMPTION();
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    				}
    				#endif
    
    				/* 退出临界区 */
    				taskEXIT_CRITICAL();
    
    				/* 成功 */
    				return pdPASS;
    			}
    			/* 目前队列已经满了,且不是覆盖型插入 */
    			else
    			{
    				/* 阻塞时间为0 */
    				if(xTicksToWait == (TickType_t)0)
    				{
    					taskEXIT_CRITICAL();
    
    					traceQUEUE_SEND_FAILED(pxQueue);
    
    					/* 返回队列已满错误 */
    					return errQUEUE_FULL;
    				}
    				/* 当前节拍状态还未记录 */
    				else if(xEntryTimeSet == pdFALSE)
    				{
    					/* 记录当前节拍状态 */
    					vTaskInternalSetTimeOutState(&xTimeOut);
    					/* 当前节拍状态已经记录 */
    					xEntryTimeSet = pdTRUE;
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    			}
    		}
    		/* 退出临界区 */
    		taskEXIT_CRITICAL();
    
    		/* 挂起调度器 */
    		vTaskSuspendAll();
    
    		/* 锁定队列 */
    		prvLockQueue(pxQueue);
    
    		/* 检查任务是否超时,并未超时 */
    		if(xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE)
    		{
    			/* 检查队列是否已满,已经满了 */
    			if(prvIsQueueFull(pxQueue) != pdFALSE)
    			{
    				traceBLOCKING_ON_QUEUE_SEND(pxQueue);
    				/* 将任务挂接到等待发送而阻塞的任务列表中,并将任务挂接到延时列表中 */
    				vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToSend), xTicksToWait);
    				/* 解锁队列 */
    				prvUnlockQueue(pxQueue);
    
    				/* 解除调度器挂起 */
    				if(xTaskResumeAll() == pdFALSE)
    				{
    					/* 请求切换 */
    					portYIELD_WITHIN_API();
    				}
    			}
    			/* 刚好队列出现空位,下一次while循环重新插入 */
    			else
    			{
    				/* 解锁队列 */
    				prvUnlockQueue(pxQueue);
    				/* 解除调度器挂起 */
    				(void)xTaskResumeAll();
    			}
    		}
    		/* 已经超时或者超时之后 */
    		else
    		{
    			/* 解锁队列 */
    			prvUnlockQueue(pxQueue);
    			/* 解除调度器挂起 */
    			(void)xTaskResumeAll();
    
    			traceQUEUE_SEND_FAILED(pxQueue);
    			
    			/* 队列已满 */
    			return errQUEUE_FULL;
    		}
    	}
    }

    下面看一下prvCopyDataToQueue函数,看看是怎么恢复优先级的

    从源代码来看是通过xTaskPriorityDisinherit函数来恢复优先级

    /* 将队列项拷贝到队列中 */
    static BaseType_t prvCopyDataToQueue(Queue_t *const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition)
    {
    	BaseType_t xReturn = pdFALSE;
    	UBaseType_t uxMessagesWaiting;
    
    	/* 队列中队列项数 */
    	uxMessagesWaiting = pxQueue->uxMessagesWaiting;
    
    	/* 队列项大小为0 */
    	if(pxQueue->uxItemSize == (UBaseType_t)0)
    	{
    		#if (configUSE_MUTEXES == 1)
    		{
    			/* 队列类型为互斥锁 */
    			if(pxQueue->uxQueueType == queueQUEUE_IS_MUTEX)
    			{
    				/* 剥夺继承优先级 */
    				xReturn = xTaskPriorityDisinherit(pxQueue->u.xSemaphore.xMutexHolder);
    				/* 互斥锁持有者设置为空 */
    				pxQueue->u.xSemaphore.xMutexHolder = NULL;
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    		#endif
    	}
    	/* 队列项大小不为0,从队列尾部插入 */
    	else if(xPosition == queueSEND_TO_BACK)
    	{
    		/* 将队列项插入队列 */
    		(void)memcpy((void *)pxQueue->pcWriteTo, pvItemToQueue, (size_t)pxQueue->uxItemSize);
    		/* 将队列项写入指针向后偏移一个队列项 */
    		pxQueue->pcWriteTo += pxQueue->uxItemSize;
    		
    		/* 队列项写入指针已经偏移到队列尾部了 */
    		if(pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail)
    		{
    			/* 将队列项写入指针偏移到头部 */
    			pxQueue->pcWriteTo = pxQueue->pcHead;
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    	}
    	/* 队列项大小不为0,从队列头部插入/覆盖插入 */
    	else
    	{
    		/* 将队列项插入队列 */
    		(void)memcpy((void *)pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, (size_t)pxQueue->uxItemSize);
    		/* 将队列项读出指针向前偏移一个队列项 */
    		pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;
    		
    		/* 队列项读出指针已经偏移到队列头部了 */
    		if(pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead)
    		{
    			/* 将队列项读出指针偏移到尾部 */
    			pxQueue->u.xQueue.pcReadFrom = (pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize);
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    
    		/* 覆盖式插入 */
    		if(xPosition == queueOVERWRITE)
    		{
    			/* 队列项个数大于0 */
    			if(uxMessagesWaiting > (UBaseType_t)0)
    			{
    				/* 因为覆盖了一个队列项,所以队列项数减一 */
    				--uxMessagesWaiting;
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    	}
    
    	/* 队列项数加一 */
    	pxQueue->uxMessagesWaiting = uxMessagesWaiting + (UBaseType_t)1;
    
    	return xReturn;
    }

     

     

    创建递归互斥锁

    递归互斥锁和互斥锁的创建过程是一样的,只是互斥锁的类型不同

    #define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)

     

     

    获取递归互斥锁

    和互斥锁不同的是,同一任务可以多次获取递归互斥锁。因此,主要步骤为:

    1.如果当前任务不持有该互斥锁,则申请持有互斥锁,递归次数加一

    2.如果当前任务已经持有该互斥锁,则直接递归次数加一 

    /* 获取递归互斥锁 */
    BaseType_t xQueueTakeMutexRecursive(QueueHandle_t xMutex, TickType_t xTicksToWait)
    {
    	BaseType_t xReturn;
    	Queue_t *const pxMutex = (Queue_t *)xMutex;
    
    	configASSERT(pxMutex);
    
    	traceTAKE_MUTEX_RECURSIVE(pxMutex);
    
    	/* 互斥锁持有者为当前任务 */
    	if(pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle())
    	{
    		/* 递归次数加一 */
    		(pxMutex->u.xSemaphore.uxRecursiveCallCount)++;
    		xReturn = pdPASS;
    	}
    	/* 互斥锁持有者不为当前任务 */
    	else
    	{
    		/* 持有该互斥锁 */
    		xReturn = xQueueSemaphoreTake(pxMutex, xTicksToWait);
    		if(xReturn != pdFAIL)
    		{
    			/* 递归次数加一 */
    			(pxMutex->u.xSemaphore.uxRecursiveCallCount)++;
    		}
    		else
    		{
    			traceTAKE_MUTEX_RECURSIVE_FAILED(pxMutex);
    		}
    	}
    
    	return xReturn;
    }

     

     

    释放递归互斥锁

    和互斥锁不同的是,同一任务可以多次获取递归互斥锁,解除同等次数才能彻底释放。因此,主要步骤为:

    1.递归次数减一

    2.当递归次数减完,彻底释放互斥锁

    /* 释放递归互斥锁 */
    BaseType_t xQueueGiveMutexRecursive(QueueHandle_t xMutex)
    {
    	BaseType_t xReturn;
    	Queue_t *const pxMutex = (Queue_t *)xMutex;
    
    	configASSERT(pxMutex);
    
    	/* 互斥锁持有者为当前任务 */
    	if(pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle())
    	{
    		traceGIVE_MUTEX_RECURSIVE(pxMutex);
    
    		/* 递归次数减一 */
    		(pxMutex->u.xSemaphore.uxRecursiveCallCount)--;
    
    		/* 递归次数减到0 */
    		if(pxMutex->u.xSemaphore.uxRecursiveCallCount == (UBaseType_t)0)
    		{
    			/* 彻底释放互斥锁 */
    			(void)xQueueGenericSend(pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK);
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    
    		xReturn = pdPASS;
    	}
    	/* 互斥锁持有者不为当前任务,直接返回错误 */
    	else
    	{
    		xReturn = pdFAIL;
    
    		traceGIVE_MUTEX_RECURSIVE_FAILED(pxMutex);
    	}
    
    	return xReturn;
    }

     

     

    优先级继承和剥夺

    从上面的分析,我们知道优先级继承和剥夺函数如下

    优先级继承:xTaskPriorityInherit

    互斥锁阻塞超时后,恢复优先级:prvGetDisinheritPriorityAfterTimeout和vTaskPriorityDisinheritAfterTimeout

    解放互斥锁,恢复优先级:xTaskPriorityDisinherit

     

    下面我们一个一个来分析,首先是xTaskPriorityInherit

    1.当前任务优先级和互斥锁持有任务优先级进行比较,将互斥锁持有任务的优先级更新为更高的那个优先级

    2.将互斥锁持有任务按照新的优先级重新放到就绪列表中

    /* 任务优先级继承 */
    BaseType_t xTaskPriorityInherit(TaskHandle_t const pxMutexHolder)
    {
    	TCB_t *const pxMutexHolderTCB = pxMutexHolder;
    	BaseType_t xReturn = pdFALSE;
    
    	/* 互斥锁持有者不为空 */
    	if(pxMutexHolder != NULL)
    	{
    		/* 互斥锁持有者优先级小于当前任务优先级 */
    		if(pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority)
    		{
    			/* 将互斥锁持有者的事件列表项值(任务优先级)设置为当前任务事件列表项值(任务优先级) */
    			if((listGET_LIST_ITEM_VALUE(&(pxMutexHolderTCB->xEventListItem)) & taskEVENT_LIST_ITEM_VALUE_IN_USE) == 0UL)
    			{
    				listSET_LIST_ITEM_VALUE(&(pxMutexHolderTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)pxCurrentTCB->uxPriority);
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    
    			/* 判断互斥锁持有者是否就绪,就绪 */
    			if(listIS_CONTAINED_WITHIN(&(pxReadyTasksLists[pxMutexHolderTCB->uxPriority]), &(pxMutexHolderTCB->xStateListItem)) != pdFALSE)
    			{
    				/* 将互斥锁持有者从就绪列表中移除,移除完后列表中没有任务 */
    				if(uxListRemove(&(pxMutexHolderTCB->xStateListItem)) == (UBaseType_t)0)
    				{
    					/* 检查就绪列表中是否有任务,如果没有将该优先级从当前任务优先级记录中清除 */
    					taskRESET_READY_PRIORITY(pxMutexHolderTCB->uxPriority);
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    
    				/* 互斥锁持有者优先级设置为当前任务优先级 */
    				pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
    				/* 将互斥锁持有者重新插入新优先级的就绪列表中 */
    				prvAddTaskToReadyList(pxMutexHolderTCB);
    			}
    			/* 互斥锁持有者并不在就绪态 */
    			else
    			{
    				/* 互斥锁持有者优先级设置为当前任务优先级 */
    				pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
    			}
    
    			traceTASK_PRIORITY_INHERIT(pxMutexHolderTCB, pxCurrentTCB->uxPriority);
    
    			/* 返回继承了优先级 */
    			xReturn = pdTRUE;
    		}
    		/* 互斥锁持有者优先级不小于当前任务优先级 */
    		else
    		{
    			/* 互斥锁持有者基础优先级小于当前任务优先级,说明继承了其它任务优先级 */
    			if(pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority)
    			{
    				/* 返回继承了优先级 */
    				xReturn = pdTRUE;
    			}
    			/* 没有继承优先级 */
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    	}
    	else
    	{
    		mtCOVERAGE_TEST_MARKER();
    	}
    
    	return xReturn;
    }

     

    prvGetDisinheritPriorityAfterTimeout和vTaskPriorityDisinheritAfterTimeout

    用法

    UBaseType_t uxHighestWaitingPriority;
    
    /* 获取剩下所有等待互斥锁任务的最高优先级 */
    uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout(pxQueue);
    /* 超时之后剥夺继承优先级 */
    vTaskPriorityDisinheritAfterTimeout(pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority);

    prvGetDisinheritPriorityAfterTimeout获取超时后可能需要剥夺的优先级

    1.获取等待互斥锁阻塞任务中的最高优先级

    /* 获取剩下所有等待互斥锁任务的最高优先级 */
    static UBaseType_t prvGetDisinheritPriorityAfterTimeout(const Queue_t *const pxQueue)
    {
    	UBaseType_t uxHighestPriorityOfWaitingTasks;
    
    	/* 因为拿不到互斥锁而阻塞的任务列表不为空 */
    	if(listCURRENT_LIST_LENGTH(&(pxQueue->xTasksWaitingToReceive)) > 0U)
    	{
    		/* 获取队列中优先级最高的任务 */
    		uxHighestPriorityOfWaitingTasks = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)listGET_ITEM_VALUE_OF_HEAD_ENTRY(&(pxQueue->xTasksWaitingToReceive));
    	}
    	/* 因为拿不到互斥锁而阻塞的任务列表为空 */
    	else
    	{
    		/* 最高的优先级为空闲任务优先级 */
    		uxHighestPriorityOfWaitingTasks = tskIDLE_PRIORITY;
    	}
    
    	return uxHighestPriorityOfWaitingTasks;
    }

    vTaskPriorityDisinheritAfterTimeout超时后剥夺优先级

    1.先计算剥夺继承当前任务优先级后,需要重新设置的继承优先级

    2.检查互斥锁持有者的优先级是否继承自当前任务

    3.如果互斥锁持有者的优先级是否继承自当前任务,那么就需要重新设置的继承优先级

    4.设置新的优先级,并将互斥锁持有者从原就绪列表中转移到新的就绪列表中

    注意:FreeRTOS中处理的并不完美,如果互斥锁持有者如果持有不止一把锁的话,即使互斥锁持有者的优先级是否继承自当前任务也不进行剥夺,这可能导致互斥锁的持有任务优先级并不完全准确。

    /* 超时之后剥夺继承优先级 */
    void vTaskPriorityDisinheritAfterTimeout(TaskHandle_t const pxMutexHolder, UBaseType_t uxHighestPriorityWaitingTask)
    {
    	TCB_t *const pxTCB = pxMutexHolder;
    	UBaseType_t uxPriorityUsedOnEntry, uxPriorityToUse;
    	const UBaseType_t uxOnlyOneMutexHeld = (UBaseType_t)1;
    
    	/* 互斥锁持有者不为空 */
    	if(pxMutexHolder != NULL)
    	{
    		configASSERT(pxTCB->uxMutexesHeld);
    
    		/* 互斥锁持有者基础优先级小于等待持有互斥锁任务的最高优先级,说明需要继承优先级 */
    		if(pxTCB->uxBasePriority < uxHighestPriorityWaitingTask)
    		{
    			/* 将要被设置的优先级 */
    			uxPriorityToUse = uxHighestPriorityWaitingTask;
    		}
    		/* 不需要继承优先级 */
    		else
    		{
    			/* 将要被设置的优先级 */
    			uxPriorityToUse = pxTCB->uxBasePriority;
    		}
    
    		/* 互斥锁持有者优先级不等于剩下任务最高优先级,说明之前互斥锁持有者优先级继承于当前任务 */
    		if(pxTCB->uxPriority != uxPriorityToUse)
    		{
    			/* 互斥锁持有者只持有一个互斥锁 */
    			if(pxTCB->uxMutexesHeld == uxOnlyOneMutexHeld)
    			{
    				configASSERT(pxTCB != pxCurrentTCB);
    
    				traceTASK_PRIORITY_DISINHERIT(pxTCB, pxTCB->uxBasePriority);
    				
    				/* 保存任务优先级 */
    				uxPriorityUsedOnEntry = pxTCB->uxPriority;
    				/* 互斥锁持有者优先级设置为剩下等待互斥锁任务的最高优先级 */
    				pxTCB->uxPriority = uxPriorityToUse;
    
    				/* 将事件列表值也改成剩下等待互斥锁任务的最高优先级 */
    				if((listGET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem)) & taskEVENT_LIST_ITEM_VALUE_IN_USE) == 0UL)
    				{
    					listSET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriorityToUse);
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    
    				/* 把任务从当前就绪列表中移除 */
    				if(listIS_CONTAINED_WITHIN(&(pxReadyTasksLists[uxPriorityUsedOnEntry] ), &(pxTCB->xStateListItem)) != pdFALSE)
    				{
    					if(uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
    					{
    						taskRESET_READY_PRIORITY(pxTCB->uxPriority);
    					}
    					else
    					{
    						mtCOVERAGE_TEST_MARKER();
    					}
    
    					/* 重新添加到新优先级就绪列表中 */
    					prvAddTaskToReadyList(pxTCB);
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    	}
    	else
    	{
    		mtCOVERAGE_TEST_MARKER();
    	}
    }

     

    xTaskPriorityDisinherit,剥夺优先级继承

    1.先判断是否继承了优先级

    2.如果继承了优先级,恢复基础优先级,并将任务根据新优先级从原就绪列表中转移到新的就绪列表

    注意:FreeRTOS中处理的并不完美,如果互斥锁持有者如果持有不止一把锁的话,并不会剥夺继承优先级,这可能导致互斥锁的持有任务优先级并不完全准确。

    /* 剥夺继承优先级 */
    BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder)
    {
    	TCB_t *const pxTCB = pxMutexHolder;
    	BaseType_t xReturn = pdFALSE;
    
    	/* 互斥锁持有者不为空 */
    	if(pxMutexHolder != NULL)
    	{
    		configASSERT(pxTCB == pxCurrentTCB);
    		configASSERT(pxTCB->uxMutexesHeld);
    		
    		/* 任务持有锁数量减一 */
    		(pxTCB->uxMutexesHeld)--;
    
    		/* 任务优先级不等于基础优先级,说明继承了优先级 */
    		if(pxTCB->uxPriority != pxTCB->uxBasePriority)
    		{
    			/* 任务持有锁数量为0 */
    			if(pxTCB->uxMutexesHeld == (UBaseType_t)0)
    			{
    				/* 将互斥锁持有者从就绪列表中移除 */
    				if(uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
    				{
    					/* 检查就绪列表中是否有任务,如果没有将该优先级从当前任务优先级记录中清除 */
    					taskRESET_READY_PRIORITY(pxTCB->uxPriority);
    				}
    				else
    				{
    					mtCOVERAGE_TEST_MARKER();
    				}
    
    				traceTASK_PRIORITY_DISINHERIT(pxTCB, pxTCB->uxBasePriority);
    				
    				/* 将任务优先级复原为基础优先级 */
    				pxTCB->uxPriority = pxTCB->uxBasePriority;
    
    				/* 将事件列表项值重新设为基础优先级值 */
    				listSET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)pxTCB->uxPriority);
    				/* 将任务重新加入新优先级任务列表 */
    				prvAddTaskToReadyList(pxTCB);
    
    				xReturn = pdTRUE;
    			}
    			else
    			{
    				mtCOVERAGE_TEST_MARKER();
    			}
    		}
    		else
    		{
    			mtCOVERAGE_TEST_MARKER();
    		}
    	}
    	else
    	{
    		mtCOVERAGE_TEST_MARKER();
    	}
    
    	return xReturn;
    }

     

     

    展开全文
  • 一个多线程访问的同一个资源,java synchronized互斥锁的用法,android和此用法一致。
  • linux-互斥锁与死锁 线程安全 再次了解原子性 如何保证线程安全 互斥锁 加锁与解锁 互斥锁的使用流程 死锁 产生死锁的两种方式 死锁产生的条件 避免死锁的方式 之前我们了解到一些线程的基本知识,线程等待,线程...

    之前我们了解到一些线程的基本知识,线程等待,线程分离啊什么的。现在我们用这些知识简单实现一个火车站抢票的功能。
    假设一共有100张票,我们开放4个窗口(线程),让每个窗口进行卖票的功能,每个窗口之间是独立的,他们的任务就是卖完这100张票,每卖一张票,就让总票数-1。

    void* ThreadStart(void* arg)
    {
       (void*)arg;
     
       while(1)
       {
         if(g_tickets > 0)
         {
           g_tickets--; //总票数-1
           //usleep(100000); //模拟一个窗口的堵塞                                                                                                                                                  
           printf("i am thread [%p],I'll sell ticket number [%d]\n",\
                     pthread_self(),g_tickets + 1);
         }                                  
         else                               
         {                                  
           break;//没有票了,直接返回该线程 
         }                                  
       }                                    
       return NULL;                         
    } 
    

    这样写每个线程的任务,看上去好像是没有什么问题,先看看运行结果
    在这里插入图片描述
    好像真的没有什么问题,但是这是建立在每个线程执行一个任务都是很快的情况下,我们现实中每一个买票的过程所花费的时间都不短,这可以理解成一种阻塞。我们在程序中模拟一下这个阻塞的过程,看看会出现什么结果。
    在这里插入图片描述
    不得了了,我们发现好像有几张票没卖出去,又好像1号票被卖了四次,不同的窗口出售了同样的一张票,结果出现了二义性,这个问题很严重,怎么解决呢?这就得用线程安全的知识了

    线程安全

    通过上面的代码,我们发现多个线程同时运行的时候,在访问临界资源后,使得程序出现了二义性的结果。
    线程安全就是为了解决多个线程在同时运行时,在访问临界资源的同时不能让程序出现二义性的结果。

    • 临界资源:在同一时刻,一个资源只能被一个线程(执行流)所访问
    • 访问:在临界区中对临界资源进行非原子操作
    • 原子操作:原子操作使得当前操作是一步完成的,也就是每一个操作只能有两个结果,一个是完成,一个是未完成

    再次了解原子性

    在这里插入图片描述

    1. 执行流A先从CPU中获得数据 g_tickets = 100,在获取完数据之后发生了阻塞,开始处理这个数据,此时还没有执行 g_tickets-- 的操作。
    2. 在执行流A处理第100张票的同时,执行流B开始从CPU中获取数据,此时因为执行流A还没有进行 g_tickets-- 的操作,CPU中 g_tickets = 100,执行流B开始处理这个数据,进行 g_tickets–,然后返回给CPU处理后的结果,g_tickets = 99。
    3. 之后执行流A在执行 g_tickets-- 之后,返回给CPU的结果是 g_tickets = 99。

    经过这样一个模拟阻塞的过程,发现原本应该是 g_tickets = 98 的结果,却因为二义性导致结果是 g_tickets = 99。这就是由于执行流A执行的 g_tickets-- 操作是非原子的操作,也就是执行流A在执行的时候,可能会遇到时间片耗尽,从而导致执行流A被调度。相当于执行流A在执行时的任何一个地方都可能会被打断。

    如何保证线程安全

    • 互斥:保证在同一时刻只能有一个执行流访问临界资源,如果多个执行流想要同时问临界资源,并且临界资源没有执行流在执行,那么只能允许一个执行流进入该临界区。
      也就是如果执行流A,B,C…想要同时访问临界资源,这时就只能有一个执行流先访问临界资源,假设此时访问的是执行流A,其他执行流B,C…不能打断执行流A的执行。
    • 同步:保证程序对临界资源的合理访问。也就是执行流A执行完自己的任务时,必须让出CPU资源,不能一直占着CPU,应该及时让出CPU,使其他执行流也可以执行他们自己的任务。

    在这里插入图片描述
    看着图片“形象”的再来理解一次,假设有一个厕所,互斥就是同一时间只能有一个滑稽去上厕所,其他滑稽只能在外面排队;同步就是滑稽A上完厕所后不能占着茅坑不拉* ,应该赶紧出去让出坑位给其他滑稽。

    想要做到这几点,本质上就是需要一把锁,也就是互斥量 (mutex)
    在这里插入图片描述

    互斥锁

    互斥锁是用来保证互斥属性的一种操作
    互斥锁的底层是互斥量,而互斥量**(mutex)**的本质是一个计数器,这个计数器只有两个状态,即 0 和 1 。

    • 0:表示无法获取互斥锁,也就是需要访问的临界资源不可以被访问
    • 1:表示可以获取互斥锁,也就是需要访问的临界资源可以被访问

    在这里插入图片描述

    加锁与解锁

    加锁的过程可以使用互斥锁提供的接口,以此来获取互斥锁资源

    • 互斥量计数器的值为1,表示可以获取互斥锁,获取到互斥锁的接口就正常返回,然后访问临界资源。
    • 互斥量计数器的值为0,表示不可以获取互斥锁,当前获取互斥锁的接口进行阻塞等待。

    加锁操作:对互斥锁当中的互斥量保存的计数器进行减1操作
    解锁操作:对互斥锁当中的互斥量保存的计数器进行加1操作

    看到这里,就可以简单的理解为加锁和解锁就是这样的一个过程在这里插入图片描述
    那么问题来了,我们在购票代码中改变票数的操作就是 g_tickets–,这个的本质就是减一。互斥量计数器本身也是一个变量,这个变量的取值是0/1,对于这样一个变量进行加一减一操作的时候,这就是原子性操作吗?

    这时候不禁想起老爹的那句话
    在这里插入图片描述
    所以说,想要解决原子性的问题,还得要用原子性的操作,怎么一步就判断有没有加锁呢?
    在这里插入图片描述
    汇编中有一个指令xchgb,可以用来交换寄存器和内存中的内容,这个操作就是原子性的,一步到位。
    我们再来分析一下,如果是需要加锁的情况,互斥量计数器最后就会从1变成0;如果是不能加锁的情况,互斥量计数器中的值还是0,也就是判断之后,互斥量计数器的值都会变成0。所以我们可以在寄存器中存一个数字0,然后用这个数字0和互斥量计数器中的内容进行交换,一步到位,然后我们再根据这个寄存器交换后的值来判断加锁的情况。

    当交换完毕之后,判断寄存器中的值的两种情况

    • 如果寄存器中的值为0,则表示不能进行加锁,意味着当前加锁操作就会被阻塞,也就是不能访问这个临界资源
    • 如果寄存器中的值为1,则表示可以进行加锁,互斥量计数器与内存的值进行xchgb交换操作,就相当于给互斥量计数器进行了一个减一的操作,然后该执行流就可以获得被锁着的资源,并且加锁操作返回,从而去访问临界资源

    再次总结一下

    • 当互斥量计数器的值为1的时候,表示可以进行加锁。然后把寄存器和内存的值进行xchgb交换之后,就相当于给计数器进行了减一操作
    • 当互斥量计数器的值为0的时候,表示不可以加锁。这个时候把寄存器和内存的值进行xchgb交换之后,并没有影响到原互斥量计数器数据的真实性,当前的执行流就被挂起等待了。

    互斥锁的使用流程

    1.定义互斥锁

    • pthread_mutex_t:互斥锁变量的类型
    pthread_mutex_t lock;
    

    2.初始化互斥锁

    方法一:
    #include <pthread.h>
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                  const pthread_mutexattr_t *restrict attr);
    
    • mutex:互斥锁变量,传参的时候传入互斥锁变量的地址
    • attr:互斥锁的属性,一般情况下采用默认属性,传入一个参数NULL,让操作系统去处理细节的操作
    方法二:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //pthread_mutex_initializer
    

    pthread_mutexattr_t 本身是一个结构体的类型,我们可以用 PTHREAD_MUTEX_INITIALIZER 宏定义一个结构体的值,使用这种初始化的方法可以直接填充 pthread_mutexattr_t 这个结构体
    在这里插入图片描述
    3.加锁

    方法一:
    #include <pthread.h>
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    
    • mutex:传入互斥锁变量的地址,来进行加锁操作
      使用该接口进行加锁操作的时候:
      如果计数器当中的值为1,意味着可以加锁,加锁操作之后,计数器当中的1变成0
      如果计数器当中的值为0,意味着不可以加锁,该接口进行阻塞等待,执行流就不会向下继续执行了
    方法二:
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    • mutex:传入互斥锁变量的地址,来进行加锁操作
      使用该接口进行加锁操作的时候:
      如果计数器当中的值为1,意味着可以加锁,加锁操作之后,计数器当中的1变成0
      如果计数器当中的值为0,意味着不可以加锁,该接口直接返回,不进行阻塞等待,返回值为EBUSY(表示拿不到互斥锁)

    所以说,一般在采用 pthread_mutex_trylock 加锁的方式时,做一个循环加锁的操作,防止因为拿不到临界资源而直接返回,进而在代码总直接访问临界资源,从而导致程序产生二义性的结果。

    方法三:带有超时时间的加锁接口
    #include <pthread.h>
    #include <time.h>
    int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abs_timeout);
    
    • mutex:传入互斥锁变量的地址,来进行加锁操作
    • abs_timeout:加锁超时时间。当加锁的时候,如果超过设置的超时时间还没有获取到互斥锁的时候,则进行报错返回,并不会进行阻塞等待,返回值为ETIMEOUT
      struct timespace 有两个变量,一个代表秒,一个代表纳秒
      在这里插入图片描述

    4.解锁

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    

    不管是对 pthread_mutex_lock ,pthread_mutex_trylock,还是pthread_mutex_timedlock进行加锁操作,使用该函数都可以进行一个解锁,“万能钥匙”。

    5.销毁互斥锁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

    互斥锁销毁接口,如果使用互斥锁完成之后,如果不调用销毁接口,就会造成内存泄漏的问题。

    我们再来完善一下那个买票的程序

    1. 互斥锁的定义:因为他通常需要在不同的地方使用,所以定义为全局变量会比较方便一点;如果是C++程序的话,可以定义在类的成员变量中。
    2. 初始化:在创建线程之前就进行初始化
    3. 销毁:在所有线程退出之后进行销毁。如果还有线程没有退出就销毁了互斥锁,其他线程就会出现卡顿,死锁。
    4. 加锁的地方:是他访问临界资源的地方之前
    5. 解锁的地方:必须在所有有可能退出程序的地方都要解一个锁。如果一个进程加锁之后,该执行流直接退出掉,就会使得其他想要获取该互斥锁的进程卡死。
    6. 如果一个执行流加锁了,但是没有进行相应的解锁操作,就会使其他想获取该互斥锁的执行流陷入进程阻塞。
      在这里插入图片描述
    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    #define THREADNUM 4   //4个线程来当做购票的窗口
    int g_tickets = 100;  //100张票
    pthread_mutex_t lock; 定义一个互斥锁变量
    
    void* ThreadStart(void* arg)
    {
      (void*)arg;
    
      while(1)
      {
        pthread_mutex_lock(&lock);
        if(g_tickets > 0)
        {
          	g_tickets--; //总票数-1
        	usleep(10000); //模拟一个
    
          	printf("i am thread [%p],I'll sell ticket number [%d]\n",\
                    pthread_self(),g_tickets + 1);
        }
        else 
        {
         	//假设有一个执行流判断了g_tickets之后发现,g_tickets的值是小于等于0的            
           //则会执行else逻辑,直接就被break跳出while循环
           //跳出while循环的执行流还加着互斥锁
           //所以在所有有可能退出线程的地方都需要进行解锁操作
           pthread_mutex_unlock(&lock);
           break;//没有票了,直接返回该线程
    
        }
        pthread_mutex_unlock(&lock);
      }
      return NULL;
    }
    
    int main()
    {
       pthread_mutex_init(&lock,NULL);//创建线程之前进行初始化
       pthread_t tid[THREADNUM];//保存线程的标识符
      
      int  i = 0;
      for(i = 0; i < THREADNUM; i++)
      {
        int ret = pthread_create(&tid[i],NULL,ThreadStart,NULL);
        if(ret < 0)
        {
          perror("pthread_create error\n");
          return 0;
        }
      }
      sleep(1);
      for(i = 0; i < THREADNUM; i++)
      {
        //线程等待 
        pthread_join(tid[i],NULL);
      }
      //锁销毁 
      pthread_mutex_destroy(&lock);
      return 0;
    }
    

    死锁

    产生死锁的两种方式

    1. 假如程序当中有一个执行流因为结束了当前线程而没有进行解锁操作,由于他没有进行解锁操作,就会使其他想要获取互斥锁的线程进行阻塞,从而产生死锁
    2. 当程序中有多个互斥锁存在的时候,两个或者多个已经上锁的线程之间互相申请对方的互斥锁资源,就会使双方都陷入永久等待的状态,从而产生死锁

    在这里插入图片描述
    对第二种产生死锁方式的解释
    假设有两个执行流(执行流A,执行流B),两个互斥锁(互斥锁1,互斥锁2)。
    两个线程任务的第一步就是上锁,执行流A先获取互斥锁1,执行流B获取互斥锁2。
    第二步,执行流A在已经上锁了互斥锁1的条件下,想要想要获取互斥锁2;与此同时,执行流B又在上锁了互斥锁2的条件下想要获取互斥锁1。两个执行流第二步想要获取的互斥锁都处于上锁的状态,同时两个执行流都处于无法停止的任务中,也就是阻塞状态。

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    
    pthread_mutex_t lock1;
    pthread_mutex_t lock2;
    
    void* ThreadA(void* arg)
    {
      (void)arg;
      //设置进程属性为结束后自动释放进程空间 
      pthread_detach(pthread_self());
      //获取互斥锁1 
      pthread_mutex_lock(&lock1);
      sleep(3);
    
      //获取互斥锁2 
      pthread_mutex_lock(&lock2);
      //解锁 
      pthread_mutex_unlock(&lock1);
      pthread_mutex_unlock(&lock2);
      return NULL;
    }
    
    void* ThreadB(void* arg)
    {
      (void)arg;
      pthread_detach(pthread_self());
      //获取互斥锁2 
      pthread_mutex_lock(&lock2);
      sleep(3);
    
      //获取互斥锁1 
      pthread_mutex_lock(&lock1);
    
      pthread_mutex_unlock(&lock2);
      pthread_mutex_unlock(&lock1);
      return NULL;
    }
    
    int main()
    {
      //互斥锁初始化 
      pthread_mutex_init(&lock1,NULL);
      pthread_mutex_init(&lock2,NULL);
      pthread_t tid[2];//模拟两个执行流 
      //创建两个线程 
      int ret = pthread_create(&tid[0],NULL,ThreadA,NULL);
      if(ret < 0)
      {
        perror("pthread_create A error");
      }
      ret = pthread_create(&tid[1],NULL,ThreadB,NULL);
      if(ret < 0)
      {
        perror("pthread_create B error");
      }
      //主线程进行等待 
      while(1)
      {
        sleep(1);
        printf("i am main thread\n");
      }
    
      //互斥锁销毁 
      pthread_mutex_destroy(&lock1);
      pthread_mutex_destroy(&lock2);
      return 0;
    }
    

    在这里插入图片描述
    再用gdb 调试一下,看看线程阻塞的地方

    查看所有线程调用堆栈的信息 --> thread apply all bt
    切换到某一个执行流 --> t [执行流编号]
    在某一个执行流中查看该执行流调用堆栈的信息 --> f [堆栈编号] 可以跳转到具体的堆栈

    查看两个锁所占有的线程号(执行流号)
    在这里插入图片描述
    查看两个线程(执行流)发生阻塞的位置
    在这里插入图片描述

    死锁产生的条件

    1. 每一把锁只能同时被一个执行流占用。互斥条件
    2. 已经占有互斥锁的执行流不能申请其他互斥锁。请求与保持
    3. 多个执行流在请求锁资源时,形成了一个闭环。循环等待条件
    4. 只有拥有互斥锁的线程才可以释放该互斥锁的资源。不可剥夺

    避免死锁的方式

    1. 每个线程加锁的顺序一致。让每个线程都是按照同一个顺序去加互斥锁。
    2. 使用完互斥锁后,及时释放锁资源
    3. 一次性分配互斥锁。在加锁之前,先判断该执行流执行过程中需要加的所有互斥锁是否都是空闲的,然后一次性给这些互斥锁都加锁,如果有锁资源被占用,就等待锁资源齐全后再进行加锁。
    4. 避免产生死锁的几种情况

    破坏请求与保持情况:

    1. 静态分配,每个进程开始执行时就申请完所需要的资源。(一次性分配互斥锁)
    2. 动态分配,每个线程在申请他所需要的资源时,它本身不占用系统资源

    破坏不可剥夺条件:
    当线程不能获得所需要的资源时,就让这个线程陷入等待状态,在等待的时候把该线程已经占有的资源隐式的释放到系统的资源列表中,让他所占有的资源可以被其他进程使用。
    这个等待的进程在他重新获得自己已有的资源以及新申请的资源才可以取消等待。

    破坏循环等待条件
    采用资源有序分配,将系统中的所有资源进行顺序编号,将紧缺的,稀少的用处大的资源采用较大的编号。
    在线程申请资源的时候,必须按照编号的顺序进行,一个线程只有获得较小编号的资源才可以申请较大编号的资源。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 183,581
精华内容 73,432
关键字:

互斥锁