基本概念
- 同步是指按预定的先后次序进行运行
- 在访问临界区的时候只允许一个 (或一类) 线程运行
进入 / 退出临界区的方式
1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。
2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。
信号量
- 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的
- 假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)
控制块
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_t rt_sem_create(const char *name,
rt_uint32_t value,
rt_uint8_t flag);
flag可选:
RT_IPC_FLAG_FIFO //非实时性
RT_IPC_FLAG_PRIO,即确保线程的实时性
删除
rt_err_t rt_sem_delete(rt_sem_t sem);
静态
创建
rt_err_t rt_sem_init(rt_sem_t sem,
const char *name,
rt_uint32_t value,
rt_uint8_t flag)
删除
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);
信号量使用例子
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 5
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{
static rt_uint8_t count = 0;
while(1)
{
if(count <= 100)
{
count++;
}
else
return;
/* count 每计数 10 次,就释放一次信号量 */
if(0 == (count % 10))
{
rt_kprintf("t1 release a dynamic semaphore.\n");
rt_sem_release(dynamic_sem);
}
}
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{
static rt_err_t result;
static rt_uint8_t number = 0;
while(1)
{
/* 永久方式等待信号量,获取到信号量,则执行 number 自加的操作 */
result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
if (result != RT_EOK)
{
rt_kprintf("t2 take a dynamic semaphore, failed.\n");
rt_sem_delete(dynamic_sem);
return;
}
else
{
number++;
rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number);
}
}
}
/* 信号量示例的初始化 */
int semaphore_sample(void)
{
/* 创建一个动态信号量,初始值是 0 */
dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);
if (dynamic_sem == RT_NULL)
{
rt_kprintf("create dynamic semaphore failed.\n");
return -1;
}
else
{
rt_kprintf("create done. dynamic semaphore value = 0.\n");
}
rt_thread_init(&thread1,
"thread1",
rt_thread1_entry,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
rt_thread_startup(&thread1);
rt_thread_init(&thread2,
"thread2",
rt_thread2_entry,
RT_NULL,
&thread2_stack[0],
sizeof(thread2_stack),
THREAD_PRIORITY-1, THREAD_TIMESLICE);
rt_thread_startup(&thread2);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);
信号量的使用场景
信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程、中断与线程间的同步中。
互斥量
信号量的问题
- 使用信号量会导致的另一个潜在问题是线程优先级翻转问题
- 在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)
互斥量控制块
struct 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; /* 当前拥有互斥量的线程 */
};
/* rt_mutext_t 为指向互斥量结构体的指针类型 */
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);
窥探互斥锁
rt_err_t rt_mutex_trytake(rt_mutex_t mutex);
释放互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex);
互斥量使用示例
#include <rtthread.h>
#define THREAD_PRIORITY 8
#define THREAD_TIMESLICE 5
/* 指向互斥量的指针 */
static rt_mutex_t dynamic_mutex = RT_NULL;
static rt_uint8_t number1,number2 = 0;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread_entry1(void *parameter)
{
while(1)
{
/* 线程 1 获取到互斥量后,先后对 number1、number2 进行加 1 操作,然后释放互斥量 */
rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
number1++;
rt_thread_mdelay(10);
number2++;
rt_mutex_release(dynamic_mutex);
}
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread_entry2(void *parameter)
{
while(1)
{
/* 线程 2 获取到互斥量后,检查 number1、number2 的值是否相同,相同则表示 mutex 起到了锁的作用 */
rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
if(number1 != number2)
{
rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2);
}
else
{
rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1);
}
number1++;
number2++;
rt_mutex_release(dynamic_mutex);
if(number1>=50)
return;
}
}
/* 互斥量示例的初始化 */
int mutex_sample(void)
{
/* 创建一个动态互斥量 */
dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO);
if (dynamic_mutex == RT_NULL)
{
rt_kprintf("create dynamic mutex failed.\n");
return -1;
}
rt_thread_init(&thread1,
"thread1",
rt_thread_entry1,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
rt_thread_startup(&thread1);
rt_thread_init(&thread2,
"thread2",
rt_thread_entry2,
RT_NULL,
&thread2_stack[0],
sizeof(thread2_stack),
THREAD_PRIORITY-1, THREAD_TIMESLICE);
rt_thread_startup(&thread2);
return 0;
}
/* 导出到 MSH 命令列表中 */
MSH_CMD_EXPORT(mutex_sample, mutex sample);
事件集
-
事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步
-
可以实现满足多个条件时候才实现同步
-
这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 逻辑与 或逻辑或将一个或多个事件关联起来,形成事件组合。事件的 逻辑或也称为是独立型同步,指的是线程与任何事件之一发生同步;事件逻辑与 也称为是关联型同步,指的是线程与若干事件都发生同步。
事件集有以下特点:
- 事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
- 事件仅用于同步,不提供数据传输功能
- 事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。
事件信息标记
每个线程都拥有一个事件信息标记,它有三个属性,分别是
- RT_EVENT_FLAG_AND(逻辑与),
- RT_EVENT_FLAG_OR(逻辑或)
- RT_EVENT_FLAG_CLEAR(清除标记)
事件集控制块
struct rt_event
{
struct rt_ipc_object parent; /* 继承自 ipc_object 类 */
/* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */
rt_uint32_t set;
};
/* rt_event_t 是指向事件结构体的指针类型 */
typedef struct rt_event* rt_event_t;
事件常用函数
动态创建事件集
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);
option 的值可取:
/* 选择 逻辑与 或 逻辑或 的方式接收事件 */
RT_EVENT_FLAG_OR
RT_EVENT_FLAG_AND
/* 选择清除重置事件标志位 */
RT_EVENT_FLAG_CLEAR
事件使用示例
#include <rtthread.h>
#define THREAD_PRIORITY 9
#define THREAD_TIMESLICE 5
#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)
/* 事件控制块 */
static struct rt_event event;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程 1 入口函数 */
static void thread1_recv_event(void *param)
{
rt_uint32_t e;
/* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
rt_kprintf("thread1: OR recv event 0x%x\n", e);
}
rt_kprintf("thread1: delay 1s to prepare the second event\n");
rt_thread_mdelay(1000);
/* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
rt_kprintf("thread1: AND recv event 0x%x\n", e);
}
rt_kprintf("thread1 leave.\n");
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程 2 入口 */
static void thread2_send_event(void *param)
{
rt_kprintf("thread2: send event3\n");
rt_event_send(&event, EVENT_FLAG3);
rt_thread_mdelay(200);
rt_kprintf("thread2: send event5\n");
rt_event_send(&event, EVENT_FLAG5);
rt_thread_mdelay(200);
rt_kprintf("thread2: send event3\n");
rt_event_send(&event, EVENT_FLAG3);
rt_kprintf("thread2 leave.\n");
}
int event_sample(void)
{
rt_err_t result;
/* 初始化事件对象 */
result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO);
if (result != RT_EOK)
{
rt_kprintf("init event failed.\n");
return -1;
}
rt_thread_init(&thread1,
"thread1",
thread1_recv_event,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
THREAD_PRIORITY - 1, THREAD_TIMESLICE);
rt_thread_startup(&thread1);
rt_thread_init(&thread2,
"thread2",
thread2_send_event,
RT_NULL,
&thread2_stack[0],
sizeof(thread2_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
rt_thread_startup(&thread2);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(event_sample, event sample);
事件集的使用场合
- 事件集可使用于多种场合,它能够在一定程度上替代信号量,用于线程间同步。
- 一个线程或中断服务例程发送一个事件给事件集对象,而后等待的线程被唤醒并对相应的事件进行处理
- 与信号量不同的是,事件的发送操作在事件未清除前,是不可累计的,而信号量的释放动作是累计的