目录
互斥量的基本概念
互斥量的优先级继承机制
互斥量的运作机制
互斥量相关接口
互斥量控制块
创建互斥量
初始化互斥量
脱离互斥量
获取互斥量
释放互斥量
使用场合
互斥量实验(优先级继承)
举个例子:怎么独享厕所?自己开门上锁,完事了自己开锁。
相比于信号量,信号量是只要有钥匙,谁都可以开锁,互斥量相当于里面的人反锁了,只能谁拥有谁释放。使用队列,信号量都可以是实现互斥访问,以信号量为例:
这需要有两个前提:
任务B很老实,不撬门(一开始不"give"信号量)
没有坏人,别的任务不会give信号量
可以看到,使用信号量确实也可以实现互斥访问,但是不完美
使用互斥量可以解决这个问题,互斥量的名字也取得很好:
它的核心在于:谁上锁,就只能由谁开锁
互斥量又称为互斥型信号量,是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权,递归访问(递归互斥量)以及防止优先级反转的特性,用于实现对临界资源的独占式处理,任意时刻互斥量的状态只有两种,开锁或者闭锁。当互斥量被线程持有时,该互斥量处于闭锁状态,这个线程获得互斥量的所有权,当该线程释放这个互斥量时,该互斥量处于开锁状态,线程失去该互斥量的所有权。当一个线程持有互斥量时,其他线程将不能再对该互斥量进行开锁或持有。持有该互斥量的线程也能够再次获得这个锁而不被挂起,这就是递归访问。这个特性与一般的二值信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,线程递归获取信号量时会发生主动挂起(最终形成死锁)。
如果想要用于实现同步(线程之间或者线程与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于线程与线程、线程与中断的同步,但是互斥量更多的是用于保护资源的互锁。
用于互锁的互斥量何以充当保护资源的令牌。当一个线程希望访问到某个资源时,它必须先获取令牌,当线程使用完资源后,必须归还令牌,以便其他线程可以访问到该资源。是不是很熟悉,在我们之前讲过的二值信号量里面也是一样的,用于保护临界资源,保证多线程的访问井然有序。当线程获取到信号量的时候才能开始被保护的资源,使用完就释放信号量,下一个线程才能获取到信号量从而可用使用被保护的资源。但是信号量会导致另一个潜在的问题,那就是线程优先级翻转。而RT-Thread提供的互斥量通过优先级继承,可以降低优先级翻转问题产生的影响,所以用于临界资源的保护一般建议使用互斥量。
在RT-Thread操作系统中为了降低优先级翻转问题利用了优先级继承算法,优先级继承算法是指,暂时提高某个占有某种资源的低优先级线程的优先级,使之与在所有等待该资源的线程中优先级最高的那个线程的优先级相等,而当这个低优先级线程执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。
互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。也就是说,某个临界资源受到一个互斥量的保护,如果这个资源正在被一个低优先级线程使用,那么此时的互斥量也是闭锁状态,也代表了没有线程能够申请到这个互斥量,如果此时一个高优先级线程想要对这个资源进行访问,也去申请这个互斥量,那么高优先级的线程就会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有互斥量的线程的优先级临时提升到与高优先级线程的优先级相同,这个优先级提升的过程就叫做优先级继承。这个优先级继承机制确保高优先级线程进入阻塞态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小。
结合示意图再说一遍,我们知道线程的优先级在创建的时候就已经是设置好的,高优先级的线程可以打断低优先级的线程,抢占CPU的使用权。但是在很多场合中,某些资源只有一个,当低优先级线程正在占用该资源的时候,即便高优先级线程也只能乖乖的等待低优先级线程使用完该资源后释放资源。这里高优先级线程无法运行而低优先级线程可以运行的现象称为“优先级翻转”。
举个例子,现在有3个线程分别为H线程(High),M线程(Middle)L线程(Low),3个线程的优先级顺序为H线程>M线程>L线程。正常运行的时候,H线程可以打断M线程和L线程,M线程可以打断L线程,假设系统中有一个资源被保护了,此时该资源被L线程正在使用中,某一刻H线程需要使用该资源,但是L线程还没有使用完,H线程则因为申请不到资源而进入阻塞态,L线程继续使用该资源,此时已经出现了“优先级翻转”的现象,高优先级线程等着低优先级线程执行,如果在L线程执行的时候刚好M线程被唤醒了,由于M线程优先级比L线程优先级高,那么会打断L线程抢占CPU的使用权,直到M线程执行完毕,再把CPU使用权归还给L线程,L线程继续运行,等到执行完毕之后释放资源,H线程此时才从阻塞态解除,使用该资源。这个过程,本来是最高优先的H线程,在等待了更低优先级的L线程和M线程,其阻塞的时间是M线程运行时间+L线程运行时间,这只是只有3个线程的系统,假如很多个这个样子的线程打断最低优先级的线程,那么这个系统最高优先级的线程岂不是崩溃了,这个现象绝对是不允许出现的,高优先级的线程必须能及时响应。所以,没有优先级继承的情况下,使用资源保护,其危害极大,具体见优先级翻转图解。
优先级翻转图解1:L 线程正在使用某个临界资源,H线程被唤醒,执行H线程.但L线程并未执行完毕,此时临界资源还未释放。
优先级翻转图解2:这个时刻H线程也要对该临界资源进行访问,但L线程还未释放资源,由于保护机制,H线程进入阻塞态,L线程得以继续运行,此时已经发生了优先级翻转现象。
优先级翻转图解3:某个时刻M线程被唤醒,由于M线程的优先级高于L线程,M线程抢占了CPU的使用权,M线程开始运行,此时L线程尚未执行完,临界资源还没释放。
优先级翻转图解4:M线程运行结束,归还CPU使用权,L线程继续运行。
优先级翻转图解5:L线程运行结束,释放临界资源,H线程得以对资源进行访问,H线程开始运行。
在这过程中,H的等待时间过长,这对系统来说是致命的,所以这种情况不允许出现,而互斥量就是用来降低优先级翻转的产生的危害。
假如有优先级继承呢?那么,在H线程申请该资源的时候,由于申请不到资源会进入阻塞态,那么系统就会把当前正在使用资源的L线程的优先级提高到与H线程的优先级相同,此时M线程被唤醒了,因为它的优先级比H线程低,所以无法打断L线程,因为此时L线程的优先级被临时提升到了H,所以L线程使用完该资源了进行释放,那么此时H线程优先级最高,将接着抢占CPU的使用权,H线程的阻塞时间仅仅是L线程的执行时间,此时优先级的危害降低到了最低,这就是优先级继承的优势。
优先级继承1:L线程正在使用某临界资源,L线程正在使用某临界资源,H线程被唤醒,执行H线程。但L线程并未执行完毕,此时临界资源还未释放。
优先级继承2:某一时刻H线程也要对该资源进行访问,由于保护机制,H线程进入阻塞态,此时发生优先级继承,系统将L线程的优先级暂时提升到与H线程优先级相同,L线程将继续执行。
优先级继承3:在某一时刻M线程被唤醒,由于此时M线程的优先级暂时低于L线程,所以M线程仅在就绪态,而无法获得CPU的使用权
优先级继承4:L线程运行完毕,H线程获得对资源的访问权,H线程从阻塞态变成运行态,此时L线程的优先级会变回原来的优先级。
优先级继承5:当H线程运行完毕,M线程得到CPU的使用权,,开始执行。
优先级继承6:系统正常运行,按照设定好的优先级运行。
这里举个生动的例子:一个公司有员工、中层领导、高级领导,一开始员工正在上厕所,高级领导也突然要上厕所,这时尽管你是高级领导,你也不能把人家从厕所拉出来吧。但是,你又不想员工完事之后,中层领导继续,你就让员工赶紧快点,这时候,中层领导只能把员工当成领导,最后到你,然后员工出来之后,你不能和领导们平起平坐(卸磨杀驴),回到最低级。
互斥量的应用场景:
互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被线程持有的时候则立即撰文闭锁的状态。互斥量更适合于:
需要注意的是:互斥量不能在中断服务函数中使用
多线程环境下会存在多个线程访问同一临界资源的场景,该资源会被线程独占处理。其他线程在资源被占用的情况下不允许对该临界资源进行访问,这个时候就需要用到互斥量来进行资源保护,那么互斥量是怎么避免这种冲突?
用互斥量处理不同线程对临界资源的同步访问时,线程要获得互斥量才能进行资源访问,如果一旦有现车成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他线程会因为获取不到互斥量而不能访问这个资源,线程会根据用户自定义的等待时间进行等待,直到互斥量被持有的线程释放后,其他线程才能获取互斥量从而得以访问临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个线程正在访问这个临界资源,保证了临界资源的安全性。
truct rt_mutex
{
struct rt_ipc_object parent; /**< 继承自IPC_Object类 */
rt_uint16_t value; /**< 互斥量的值 */
rt_uint8_t original_priority; /**< 持有线程的原始优先级 */
rt_uint8_t hold; /**< 持有线程的持有次数 */
struct rt_thread *owner; /**< 当前拥有互斥量的线程 */
};
typedef struct rt_mutex *rt_mutex_t;
rt_mutex对象从IPC中派生,由IPC容器管理。
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
参数 | 描述 |
name flag |
互斥量的名称 互斥量的标志,可以取如下类型的数值 |
删除互斥量
rt_err_t rt_mutex_delete(rt_mutex_t mutex)
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
rt_err_t rt_mutex_detach(rt_mutex_t mutex)
线程通过互斥量申请服务获取互斥量的所有权。线程对互斥量的所有权是独占的,某一个时刻一个互斥量只能被一个线程持有,获取互斥量使用下面的函数接口:
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前的线程控制,则该互斥量的持有计数加1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上等待挂起,直到其他线程释放它或者等待时间超过指定的超时时间。
当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。释放互斥量使用下面的函数接口:
rt_err_t rt_mutex_release(rt_mutex_t mutex)
使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数值就减1.当该互斥量的持有计数值为0时(即持有线程已经释放所有的持有操作),它变为空用,等待在该信号量上的线程将会被唤醒。如果线程的运行优先级被互斥量提升,当互斥量被释放后,线程恢复为持有互斥量前的优先级。
互斥量的使用比较单一,因为它是信号量的一助攻,并且它是以锁的 形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于:
#include
static struct rt_thread thread1;
static struct rt_thread thread2;
static struct rt_thread thread3;
static rt_mutex_t test_mutex = RT_NULL;
#ifdef MUTEX_PRIO_INHERIT
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static void thread1_entry(void *parameter)
{
/*先让低优先级的线程运行*/
rt_thread_mdelay(100);
/* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */
/* 检查 thread2 与 thread3 的优先级情况 */
if(thread2.current_priority != thread3.current_priority)
{
/*优先级不相同测试失败*/
rt_kprintf("the priority of thread2 is %d\n",thread2.current_priority);
rt_kprintf("the priority of thread3 is %d\n",thread3.current_priority);
rt_kprintf("test failed\n");
return ;
}
else
{
rt_kprintf("the priority of thread2 is %d\n",thread2.current_priority);
rt_kprintf("the priority of thread3 is %d\n",thread3.current_priority);
rt_kprintf("test successful!\n");
}
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
/*线程2入口*/
static void thread2_entry(void *parameter)
{
rt_err_t result;
rt_kprintf("the priority of thread2 is %d\n",thread2.current_priority);
/*先让低优先线程运行*/
rt_thread_mdelay(50);
/*试图持有互斥锁,此时thread3持有,应该把thread3的优先级提升
*到thread2相同的优先级
*/
result = rt_mutex_take(test_mutex,RT_WAITING_FOREVER);
if(result == RT_EOK)
{
/*释放互斥锁*/
rt_mutex_release(test_mutex);
}
}
ALIGN(RT_ALIGN_SIZE)
static char thread3_stack[1024];
/*线程3入口*/
static void thread3_entry(void *parameter)
{
rt_err_t result;
rt_tick_t tick;
rt_kprintf("the priority of thread3 is %d\n",thread3.current_priority);
result = rt_mutex_take(test_mutex,RT_WAITING_FOREVER);
if(result != RT_EOK)
{
rt_kprintf("thread3 take a mutex, failed\n");
}
/*做一个长时间的循环*/
tick = rt_tick_get();
while(rt_tick_get() - tick < (RT_TICK_PER_SECOND /10));
rt_mutex_release(test_mutex);
}
int mutex_sample(void)
{
rt_err_t result;
test_mutex = rt_mutex_create("test_mutex",RT_IPC_FLAG_FIFO);
if(test_mutex != RT_NULL)
rt_kprintf("test_mutex 创建成功!\n");
result = rt_thread_init(&thread1,
"thread1",
thread1_entry,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
9,
10
);
if(result == RT_EOK)
{
rt_thread_startup(&thread1);
//rt_kprintf("thread1 init successful!\n");
}
result = rt_thread_init(&thread2,
"thread2",
thread2_entry,
RT_NULL,
&thread2_stack[0],
sizeof(thread2_stack),
10,
10
);
if(result == RT_EOK)
{
rt_thread_startup(&thread2);
//rt_kprintf("thread2 init successful!\n");
}
result = rt_thread_init(&thread3,
"thread3",
thread3_entry,
RT_NULL,
&thread3_stack[0],
sizeof(thread3_stack),
11,
10
);
if(result == RT_EOK)
{
rt_thread_startup(&thread3);
//rt_kprintf("thread3 init successful!\n");
}
return 0;
}
#else
static rt_uint32_t number1,number2;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static void thread1_entry(void *parameter)
{
while(1)
{
/*线程1获取到互斥量之后,先后对number1、number2进行加1操作,然后释放互斥量*/
rt_mutex_take(test_mutex,RT_WAITING_FOREVER);
number1++;
rt_thread_mdelay(10);
number2++;
rt_mutex_release(test_mutex);
}
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static void thread2_entry(void *parameter)
{
while(1)
{
/*线程2获取到互斥量之后,检查number1、number2的值是否相同,相同则表示mutex起到了锁的作用*/
rt_mutex_take(test_mutex,RT_WAITING_FOREVER);
if(number1 == number2)
{
rt_kprintf("mutex protect number1 = number2 is %d\n",number1);
}
else
{
rt_kprintf("mutex not protect, number1 = %d,number2 = %d\n",number1,number2);
}
number1++;
number2++;
rt_mutex_release(test_mutex);
}
}
int mutex_sample(void)
{
rt_err_t result;
test_mutex = rt_mutex_create("test_mutex",RT_IPC_FLAG_FIFO);
if(test_mutex!=RT_NULL)
rt_kprintf("test_mutex create successful!\n");
result = rt_thread_init( &thread1,
"thread1",
thread1_entry,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
10,
10
);
if(result == RT_EOK)
rt_thread_startup(&thread1);
result = rt_thread_init(&thread2,
"thread2",
thread2_entry,
RT_NULL,
&thread2_stack[0],
sizeof(thread2_stack),
10,
10
);
if(result == RT_EOK)
rt_thread_startup(&thread2);
return 0;
}
#endif
MSH_CMD_EXPORT(mutex_sample,mutex_sample);