线程同步的概念
在操作系统中完成一项任务,可能往往需要多个线程协同完
例如我们显示传感器检测的数据,需要两个线程,一个接收线程,接收传感器数据,并将数据写入共享内存中;另一个显示线程,从共享内存中读取数据并显示出来
如果共享内存不是排他性(一个访问时另外的线程不可以访问),可能引起数据一致性的问题,在显示线程读取的时候,接收线程还没有完全完成数据的存放,可能会导致数据显示的不完整性;
所以要求多个线程访问同一个共享空间时候必须是互斥的(同一时间只能一个访问),在当前线程对共享内存操作完成后,才允许下一个线程对该共享内存做处理
线程的同步,其实就是线程按照一定的先后顺序有序的执行
线程同步是指多个线程通过特定的机制(信号量、互斥量、事件对象、临界区)来控制线程执行的顺序 ,如果没有线程同步,那么线程之间将会无序的
多个线程访问同一块区域,这块区域就是临界区;线程互斥就是对临界区资源访问的排他性,即任何时刻只允许一个线程访问/使用,其他线程必须等待资源。
通过线程互斥实现了线程的有序,所以线程互斥也可以称为特殊的线程同步
线程的同步有很多种方式,其核心就是在访问临界区的时候只允许一个线程运行。
在内核中,也有很多关闭/进入临界区的方法(下面两个方法少用):
- 关闭硬件中断:函数为rt_hw_interrput_disable,表示关闭中断,rt_hw_interrupt_enable()重新使能中断。线程的调度就是基于线程中断的,所以只要关闭中断,则无法进行线程调度;所以保证了对临界区访问的排他性
- 禁止调度器:rt_enter_critical()禁止调度器;rt_exit_critical()开启调度器
信号量介绍
信号量本质就是一个变量加一个队列;变量是代表可能资源、队列即等待资源的线程进行排队
信号量对象的定义:
/**
* Semaphore structure
*/
struct rt_semaphore
{
struct rt_ipc_object parent; /**< IPC变量,IPC为进程间通信 */
rt_uint16_t value; /**< 资源数量 */
rt_uint16_t reserved; /**< 保留,满足32位对齐 */
};
typedef struct rt_semaphore *rt_sem_t;
/**
* Base structure of IPC object
*/
struct rt_ipc_object
{
struct rt_object parent; /**< 已成内核对象*/
rt_list_t suspend_thread; /**< 队列,等待信号量的线程在此排队 */
};
信号量应用场景
线程与线程同步
线程同步是信号量最常见的一类应用。
使用信号量进行两个线程之间的同步,信号量的初始化为0,表示具备0个信号量资源示例;当线程尝试获取该资源的时候,将直接在这个信号量上等待
当持有信号量的线程完成它对共享资源的操作时候,释放这个信号量,那么信号量的数量递增;
然后把等待这个资源的线程唤醒,让他执行对应的操作,完成后再释放信号量,通知下一个线程
中断与线程同步
与线程与线程同步类似
我们将信号量初始化为0,当中断相应的时候,释放信号量;
当中断没有相应的时候,线程尝试获取资源的时候,因为这个信号量为0,则只能挂起等待信号量
资源计数
信号量可以作为一个递增或者递减的计数器;信号量是非负的值
资源计数适合用于线程间处理速度不匹配的场合;信号量可以作为一个线程的完成次数的计数器,当另一线程调用资源的时候,就可以知道上一线程的执行次数;实现一次调用可以处理多个信号量资源
信号量的工作机制
多个线程访问有限的共享资源受,为了保证访问的有序进行,我们可以使用信号量
当一线程要访问共享资源时,必须先获取信号量的值,如果信号量的值为非0,则线程可以访问共享资源,否则线程必须在信号量的队列上等待
RT-thread提供了多个信号量接口
创建信号量
创建信号量的方法有动态和静态方法
动态创建信号量
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);
初始化的时候需要设置信号量标志,分别为RT_IPC_FLAG_FIFO、RT_IPC_FLAG_PRIO;
RT_IPC_FLAG_FIFO为线程在队列中按照先进先出的方式排队、RT_IPC_FLAG_PRIO为线程在队列中按照优先级排序
建议使用RT_IPC_FLAG_PRIO优先级的方式,这样才能保证实时性
静态初始化信号量
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_take(rt_sem_t sem, rt_int32_t time);
rt_err_t rt_sem_trytake(rt_sem_t sem);
rt_sem_trytake即为将rt_sem_take的等待事件设置为0
rt_err_t rt_sem_trytake(rt_sem_t sem)
{
return rt_sem_take(sem, 0);
}
rt_sem_take调用时候,先判断当前信号量是否大于0,如果大于0则表示信号量获取成功;否则,信号量不可用,此线程根据函数的time参数决定是否等待,以及等待时长
- time=0时,线程不等待,在RT-thread中宏定义RT_WAITING_NO与0同意
- time>0时,线程等待time时间
- time<0时,线程永久等待,直到信号量可用,可以使用RT_WAITING_FOREVER代替
释放信号量
rt_err_t rt_sem_release(rt_sem_t sem);
使用这个函数释放信号量的时候,如果在信号量队列中有线程在排队,则唤醒等待的第一个线程;否则将信号量的值加1
删除信号量
rt_err_t rt_sem_detach(rt_sem_t sem);
rt_err_t rt_sem_delete(rt_sem_t sem);