RT-Thread移植到S5P4418(四):线程同步

同步是指按预定的先后次序进行运行,线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序,也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间将是无序的。

线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行

RT-Thread实现了三种线程间同步方式,信号量(semaphore)、互斥量(mutex)、和事件集(event)。


信号量

信号量可以实现多个同类资源的多线程互斥和同步。
特性

  • 不支持所有权,所有线程都可以操作。
  • 递归访问可能造成死锁。
  • 二值信号量类似互斥量,可能产生优先级反转。

使用场景

  • 线程同步。当持有信号量的线程完成它处理的工作时,释放这个信号量,可以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:持有信号量的线程完成它自己的工作,然后通知等待该信号量的线程继续下一部分工作。
  • 。二值信号量,保护临界区资源。
  • 中断与线程的同步。中断服务例程需要通知线程进行相应的数据处理。中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式。
  • 资源计数。例如生产者与消费者,生产者可以对信号量进行多次释放,而后消费者被调度到时能够一次处理多个信号量资源。
信号量控制块

信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。

struct rt_ipc_object{
    struct rt_object parent;

    rt_list_t suspend_thread;   /* 被挂起线程 */
};
struct rt_semaphore
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint16_t          value;                         /**< value of semaphore. */
};
typedef struct rt_semaphore *rt_sem_t;

RT-Thread移植到S5P4418(四):线程同步_第1张图片

信号量初始化

sem->parent.parent.flag信号量标志参数决定了当信号量不可用时,多个线程等待的排队方式。当选择 RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当选择 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_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);

    rt_ipc_object_init(&(sem->parent));

    sem->value = value;

    sem->parent.parent.flag = flag;

    return RT_EOK;
}
获取信号量

线程通过 rt_sem_take() 来获取信号量资源实例。当信号量值大于0时,线程获得信号量,同时该信号量值减1;当信号量值等于0时,表示资源不可用,线程通过time参数立即返回或挂起。

  • ipc.c: 119
释放信号量

释放信号量时,先判断是否有挂起线程。有则唤起一个,信号量值不变化;无则信号量值加一。

  • ipc.c: 168
工程文件

线程2释放信号量,线程1获取。

  • rtt2a9_semaphore

互斥量

互斥量只能用于单个资源的互斥访问。一种特殊的二值信号量。
特性

  • 支持所有权,只有拥有该互斥量的线程才能操作。
  • 支持递归访问。
  • 通过优先级继承算法降低优先级反转问题产生的影响。

使用场景

  • 线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。
  • 可能会由于多线程同步而造成优先级翻转的情况。
互斥量控制块
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;                 /* 当前拥有互斥量的线程 */
};
typedef struct rt_mutex* rt_mutex_t;

RT-Thread移植到S5P4418(四):线程同步_第2张图片

互斥量初始化
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag){
    RT_ASSERT(mutex != RT_NULL);

    rt_object_init(&mutex->parent.parent, RT_Object_Class_Mutex, name);

    rt_ipc_object_init(&mutex->parent);

    mutex->parent.parent.flag = flag;
    mutex->value = 1;
    mutex->original_priority = RT_THREAD_PRIORITY_MAX - 1;
    mutex->hold = 0;
    mutex->owner = RT_NULL;

    return RT_EOK;
}
获取互斥量

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。
如果互斥量已经被当前线程控制,则持有数mutex.hold加一。如果互斥量被其他线程占有,则当前线程在互斥量上挂起等待,直到其他线程释放互斥量或等待时间超时,同时,如果当前线程的优先级高于互斥量拥有者的优先级,会发生优先级继承。

  • ipc.c: 216
释放互斥量

rt_err_t rt_mutex_release(rt_mutex_t mutex)
只有互斥量的拥有者才能释放互斥量,持有数mutex.hold减一。如果持有数变成零,则判断是否有线程挂起,有则唤起。

  • ipc.c: 276
工程文件

thread1和thread2对全局变量number进行加1操作,都执行200000次,有mutex保护时,结果正确,开销大。

  • rtt2a9_mutex
    RT-Thread移植到S5P4418(四):线程同步_第3张图片

事件集

事件集用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件。

事件集控制块
struct rt_event
{
    struct rt_ipc_object parent;     /**< inherit from ipc_object */

    rt_uint32_t          set;        /* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */
};
typedef struct rt_event *rt_event_t;

RT-Thread移植到S5P4418(四):线程同步_第4张图片

发送事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
发送事件时遍历事件集的挂起线程,判断是否有线程的事件激活要求与当前 event 对象事件标志值匹配,如果有,则唤醒该线程。

  • ipc.c: 447
接收事件

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)
或者叫等待事件触发。系统首先根据 set 参数和接收选项 option 来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回。如果没有发生,则把等待的 set 和 option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。

  • ipc.c: 507
工程文件
  • rtt2a9_event

你可能感兴趣的:(RTOS,嵌入式,RT-Thread,RTOS)