临界区
多个线程操作,访问同一块区域的代码,这块代码被称为临界区
线程同步的核心思想:在访问临界区的时候,只允许一类或者一个线程运行
线程同步方式:信号量,互斥量,事件集
信号量控制块
struct rt_semaphore
{
struct rt_ipc_object parent; /* 继承自 ipc_object 类 */
rt_uint16_t value; /* 信号量的值 */
};
/* rt_sem_t 是指向 semaphore 结构体的指针类型 */
typedef struct rt_semaphore* rt_sem_t;
信号量的管理方式
创建动态信号量:rt_sem_create
删除动态信号量:rt_sem_delete
初始化静态信号量:rt_sem_init
脱离静态信号量:rt_err_t rt_sem_detach(rt_sem_t sem);
获取信号量:rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);
无等待获取信号量:rt_err_t rt_sem_trytake(rt_sem_t sem);
释放信号量;rt_err_t rt_sem_release(rt_sem_t sem);
二值信号量
信号量资源被获取了,信号量的值就是0,信号量的资源被释放了,信号量的值就是1,把这种只有0和1两种情况的信号量称为二值信号量。
二值信号量的使用:
当没有数据到来的时候,线程就进入阻塞态,不参与线程调度,等到数据来了,释放一个二值信号量,线程立即从阻塞态中删除,进入就绪态,让后运行的时候处理数据,这样系统的资源被很好的利用。
二值信号量在使用完毕需要及时释放
互斥量:
相互排斥信号量,一种特殊的二值信号量
拥有互斥量的线程拥有互斥量的所有权,只能由该持有线程释放
互斥量的两种状态:开锁和闭锁
当一个线程拥有互斥量时互斥量属于闭锁,该线程拥有该互斥量,其他线程不能拥有它并且不能对它进行开锁,保护了临界资源,当线程释放它时将对线程就行开锁。
互斥量控制块
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;
创建动态互斥量:rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t 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_trytake(rt_mutex_t mutex);
这个函数与 rt_mutex_take(mutex, RT_WAITING_NO) 的作用相同,即当线程申请的互斥量资源实例不可用的时候,它不会等待在该互斥量上,而是直接返回 - RT_ETIMEOUT。
释放互斥量:rt_err_t rt_mutex_release(rt_mutex_t mutex);
使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。
优先级翻转:
高优先级可以打断低优先级抢占CPU的使用权,当使用临界资源时候,当低优先级正在使用此资源,高优先级挂起进入阻塞态等待资源的释放,这里高优先级线程无法运行而低优先级线程运行的现象称为优先级翻转
互斥量的优先级继承机制
暂时提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程优先级相等,当该低优先级线程执行完毕后释放资源,优先级重新回到设定值。
互斥量不能在中断服务函数中使用
事件集
线程间同步机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对一,一对多线程间同步
事件集用一个32位整形变量表示,变量的每一位代表一个事件,线程通过逻辑与或逻辑或将事件关联起来,形成事件组合
每一个线程拥有一个事件信息标记
它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。
事件集的管理方式
创建事件集(动态):
rt_event_t rt_event_create(const char* name, rt_uint8_t flag);
删除事件集:
rt_err_t rt_event_delete(rt_event_t event);
初始化事件集(静态):
rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);
脱离事件集:
rt_err_t rt_event_detach(rt_event_t event);
发送事件:
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
接收事件:
rt_err_t rt_event_recv(rt_event_t event,
rt_uint32_t set,
rt_uint8_t option,
rt_int32_t timeout,
rt_uint32_t* recved);
线程唤醒之后需要设置清除事件位RT_EVENT_FLAG_CLEAR
,否者不会清除已经接受到的事件。
有两种判断rt_event_recv是否成功接收的方式
第一种:
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &recved) == RT_EOK)
{
rt_kprintf("thread1: OR recv event 0x%x\n", recved);
}
第二种:
rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &recved)
if(recved ==(EVENT_FLAG3 | EVENT_FLAG5) )
{
rt_kprintf("thread1: OR recv event 0x%x\n", recved);
}
每一个事件都要设置对应的掩码,可以将掩码打印出来,方便查看事件是否发生
#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)
如果是事件逻辑与,则rt_event_recv指向接收的事件recved是两个掩码之和
如果是事件逻辑或,则rt_event_recv指向接收的事件recved是接收到的掩码