线程同步和IPC机制
什么是"同步"
- 设备同步:在不同的设备之间规定一个共同的参考时间
- 数据库/文件同步:在不同的数据库之间保持数据一致
- 线程同步 : 在一定的时间内只允许某一个线程来访问某个资源
为什么要线程同步
- 线程同步可以避免不同优先级的线程抢占共享资源导致的错误运算或使用操作
中断锁和临界区
什么是临界区
临界资源一次仅允许一个进程使用的共享资源
临界区是指每个进程中访问临界资源的那段程序称之为临界区,且不允许长时间运行
中断锁
为了保证临界区程序实现,一般会在临界区上下上锁,防止线程发生切换;
中断锁便是其中之一实现方式;
调度器锁
线程同步常见方法
-
信号量
-
互斥量/锁(Mutex)
满足三个条件 :互斥、无死锁、无饥饿
-
读写锁
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权。但是任何希望以写模式对此锁进行加锁的线程都会阻塞。直到所有的线程释放它们的读锁为止。
-
条件变量(condition variable)
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;
RT-Thread IPC机制
信号量(Semaphore)
信号量类似建立一个标志
信号量一般分为三种:
互斥信号量:用于解决同步问题
二值信号量:用于解决互斥问题,比较特殊,可能会引起优先级反转问题
计数信号量:用于解决资源计数问题
信号量控制块
- 定义
rtdef.h文件
#ifdef RT_USING_SEMAPHORE
/**
* Semaphore structure
*/
struct rt_semaphore
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of semaphore. */
rt_uint16_t reserved; /**< reserved field */
};
typedef struct rt_semaphore *rt_sem_t;
#endif
- 信号量操作
互斥量
互斥量,也是一种信号量,但是可以避免优先级反转
互斥量的控制块和操作
#ifdef RT_USING_MUTEX
/**
* Mutual exclusion (mutex) structure
*/
struct rt_mutex
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of mutex */
rt_uint8_t original_priority; /**< priority of last thread hold the mutex */
rt_uint8_t hold; /**< numbers of thread hold the mutex */
struct rt_thread *owner; /**< current owner of mutex */
};
typedef struct rt_mutex *rt_mutex_t;
#endif
优先级继承机制
优先级继承算法是指, 暂时提高某个占有某种资源的低优先级线程的优先级,使之与在所有等待该资源的线程中优先级最高那个线程的优先级相等,而当这个低优先级线程执行完毕释放该资源时,优先级重新回到初始设定值。
互斥量具有优先级继承机制,而信号量没有。
优先级反转
我们知道线程的优先级在创建的时候就已经是设置好的,高优先级的线程可以打断低优先级的线程,抢占 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 个线程的系统,假如很多个这样子的线程打断最低优先级的线程,那这个系统最高优先级线程岂不是崩溃了,这个现象是绝对不允许出现的, 高优先级的线程必须能及时响应。所以,没有优先级继承的情况下,使用资源保护,其危害极大。
事件集
什么是事件集
事件集是一个 32 bit 的数, 每个事件用一个 bit 位代表;
事件集控制块与操作
#ifdef RT_USING_EVENT
/**
* flag defintions in event
*/
#define RT_EVENT_FLAG_AND 0x01 /**< logic and */
#define RT_EVENT_FLAG_OR 0x02 /**< logic or */
#define RT_EVENT_FLAG_CLEAR 0x04 /**< clear flag */
/*
* event structure
*/
struct rt_event
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint32_t set; /**< event set */
};
typedef struct rt_event *rt_event_t;
#endif
邮箱
单个邮件大小为4字节,刚好能够容纳一个指针。
当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。
邮箱控制块
消息队列
RT-Thread系统消息队列支持读消息超时机制
信号
接收信号的处理方式
- 类似中断的处理程序, 对于需要处理的信号, 线程可以指定处理函数, 由该函数来处理。
- 忽略某个信号, 对该信号不做任何处理, 就像未发生过一样。
- 对该信号的处理保留系统的默认值。