互斥量又叫相互排斥的信号量,是一种特殊的二值信号量,可以看成value只有0和1的二值信号量
互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它。
使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓优先级翻转,即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。
例如线程A、B、C优先级为:A>B>C.
当C持有互斥量时,A也去获取互斥量时就会被挂起,此时B的优先级高于C,所以低优先级的B会抢占C得到运行,高优先级的A反而被挂起了,这就是优先级翻转。
在 RT-Thread 操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承协议 (Sha, 1990)。优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占,
优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
rt_mutex_t mutex1;
struct rt_mutex mutex2;
rt_thread_t th1 = NULL,th2 = NULL;
//临界资源
int flag1 = 0,flag2 =0;
//IPC flags and control command definitions
// 互斥量创建flag
//#define RT_IPC_FLAG_FIFO 0x00 /**< FIFOed IPC. @ref IPC. */
//#define RT_IPC_FLAG_PRIO 0x01 /**< PRIOed IPC. @ref IPC. */
//互斥量获取flag
//#define RT_WAITING_FOREVER -1 /**< Block forever until get resource. */
//#define RT_WAITING_NO 0 /**< Non-block. */
//该标志已经作废,无论用户选择 RT_IPC_FLAG_PRIO 还是 RT_IPC_FLAG_FIFO,内核均按照 RT_IPC_FLAG_PRIO 处理
void th1_entry(void *parameter)
{
//如果信号量2的值>0, flag++
while(1)
{
rt_mutex_take(mutex1, RT_WAITING_FOREVER);
flag1++;
rt_thread_mdelay(1000); //线程1挂起,转去执行线程2,此时flag2未执行++操作
flag2++;
rt_mutex_release(mutex1);//互斥量未释放前,转去执行线程2,线程2无法获取互斥量而被挂起。
}
}
void th2_entry(void *parameter)
{
//信号量1的值大于0时,线程2才可以执行,否则挂起
while(1)
{
rt_mutex_take(mutex1, RT_WAITING_FOREVER);
flag1++;
flag2++;
rt_kprintf("th2_entry falg1 = %d,falg2 = %d\n",flag1,flag2);
rt_mutex_release(mutex1);
rt_thread_mdelay(1000);
}
}
int main(void)
{
//动态互斥量创建
mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_PRIO);//
if(mutex1 == RT_NULL){
LOG_E("mutex1 rt_mutex_create failed...\n");
return RT_ERROR;
}
LOG_D("mutex1 rt_mutex_createsuccessed...\n");
//静态互斥量创建
rt_err_t ret ;
ret = rt_mutex_init(&mutex2,"mutex2", RT_IPC_FLAG_PRIO);//
if(ret != RT_EOK){
LOG_E("mutex2 rt_mutex_create failed...\n");
return ret;
}
LOG_D("mutex2 rt_mutex_create successed...\n");
//创建两个线程用来访问临界区域
th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);
if(th1 == RT_NULL){
LOG_E("th1 rt_thread_create failed...\n");
return RT_ENOMEM;
}
LOG_D("th1 rt_thread_create successed...\n");
rt_thread_startup(th1);
th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);
if(th2 == RT_NULL){
LOG_E("th2 rt_thread_create failed...\n");
return RT_ENOMEM;
}
LOG_D("th2 rt_thread_create successed...\n");
rt_thread_startup(th2);
return RT_EOK;
}
运行结果:
[2022-10-26_16:34:34:085]msh >th2_entry falg1 = 2,falg2 = 2
[2022-10-26_16:34:36:093]th2_entry falg1 = 4,falg2 = 4
[2022-10-26_16:34:37:107]th2_entry falg1 = 6,falg2 = 6
[2022-10-26_16:34:38:105]th2_entry falg1 = 8,falg2 = 8
[2022-10-26_16:34:39:118]th2_entry falg1 = 10,falg2 = 10
[2022-10-26_16:34:40:116]th2_entry falg1 = 12,falg2 = 12
[2022-10-26_16:34:41:124]th2_entry falg1 = 14,falg2 = 14
[2022-10-26_16:34:42:120]th2_entry falg1 = 16,falg2 = 16
[2022-10-26_16:34:43:120]th2_entry falg1 = 18,falg2 = 18
[2022-10-26_16:34:44:134]th2_entry falg1 = 20,falg2 = 20
[2022-10-26_16:34:45:134]th2_entry falg1 = 22,falg2 = 22
[2022-10-26_16:34:46:141]th2_entry falg1 = 24,falg2 = 24
线程1先执行++操作,释放互斥量后线程2才能执行,所以线程2中flag从2开始打印
如果去掉线程1和2中的互斥锁,运行结果如下:
[2022-10-26_16:12:42:261]msh >th2_entry falg1 = 2,falg2 = 1
[2022-10-26_16:12:43:255]th2_entry falg1 = 4,falg2 = 3
[2022-10-26_16:12:44:264]th2_entry falg1 = 6,falg2 = 5
[2022-10-26_16:12:45:262]th2_entry falg1 = 8,falg2 = 7
[2022-10-26_16:12:46:276]th2_entry falg1 = 10,falg2 = 9
[2022-10-26_16:12:47:271]th2_entry falg1 = 12,falg2 = 11
[2022-10-26_16:12:48:279]th2_entry falg1 = 14,falg2 = 13
[2022-10-26_16:12:49:289]th2_entry falg1 = 16,falg2 = 15
[2022-10-26_16:12:50:281]th2_entry falg1 = 18,falg2 = 17
线程1对flag1++后,遇到延时,此时挂起线程1,去执行线程2,所以在线程2中flag1=1,flag2=0
执行++后打印falg1 = 2,falg2 = 1。