RT_thread(四)线程间同步之信号量


线程间同步

在多线程实时系统中,一项工作的完成往往可以通过多个线程协调的方式共同来完成,而通过这三种方式:信号量、互斥量、事件集可以保证多个线程之间同步。
线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。进入 / 退出临界区的方式有很多种:
1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。
2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。


线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序

1.信号量

以生活中的停车场为例来理解信号量的概念:

①当停车场空的时候,停车场的管理员发现有很多空车位,此时会让外面的车陆续进入停车场获得停车位;

②当停车场的车位满的时候,管理员发现已经没有空车位,将禁止外面的车进入停车场,车辆在外排队等候;

③当停车场内有车离开时,管理员发现有空的车位让出,允许外面的车进入停车场;待空车位填满后,又禁止外部车辆进入。

在此例子中,管理员就相当于信号量,管理员手中空车位的个数就是信号量的值(非负数,动态变化);停车位相当于公共资源(临界区),车辆相当于线程。车辆通过获得管理员的允许取得停车位,就类似于线程通过获得信号量访问公共资源。

1.信号量机制

信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

2.信号量控制块

信号量控制块是操作系统用于管理信号量的一个数据结构

struct rt_semaphore
{
    struct rt_ipc_object parent; //继承ipc_object结构体{rt_object(name、type、flag); suspend_thread}            

    rt_uint16_t          value;     //信号量的数值(例如空车位数量)
};
typedef struct rt_semaphore *rt_sem_t;

在这里插入图片描述
第一个parent rt_ipc_object
{struct rt_object parent;
rt_list_t suspend_thread;
}
第二个parent rt_object
{
char name[RT_NAME_MAX];
rt_uint8_t type;
rt_uint8_t flag;
}

3.信号量管理方式

Kernel中的 ipc.c

在这里插入图片描述

函数顺序按照ipc.c中顺序(内核代码顺序是按照创作顺序书写的)

1.判断是否使用信号量

#ifdef RT_USING_SEMAPHORE

信号量的开端,在rtconfig.h中是否宏定义了,才可使用信号量函数(一般都是定义的)

2.初始化和脱离

初始化(多是对系统静态句柄操作)

rt_err_t rt_sem_init(rt_sem_t    sem,//信号量对象的句柄(停车场地址)
                     const char *name,//信号量名称(停车场名字)
                     rt_uint32_t value,//信号量初始值(停车场车位数量)
                     rt_uint8_t  flag)//信号量标志等待线程队列RT_IPC_FLAG_FIFO(按顺序) 或 RT_IPC_FLAG_PRIO(按优先级)(排队顺序还是优先级顺序进出)
{
    RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在

    /* init object */
    rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);
    //sem->parent.parent的初始化(见1.2信号量控制块)

    /* init ipc object */
    rt_ipc_object_init(&(sem->parent));
    //sem->parent的初始化(见1.2信号量控制块)

    /* set init value */
    sem->value = value;//赋值

    /* set parent */
    sem->parent.parent.flag = flag;//设置标志位RT_IPC_FLAG_FIFO(按顺序) 或 RT_IPC_FLAG_PRIO(按优先级)

    return RT_EOK;
}
RTM_EXPORT(rt_sem_init);

脱离(内存继续存在,所以是脱离-内存还存在)
有初始化必有脱离

rt_err_t rt_sem_detach(rt_sem_t sem)
{
    /* parameter check */
    RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);//rt_object名字是否为信号量并返回对象类型
    RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent));
    //判断对象是否为系统对象

    /* wakeup all suspend threads */
    rt_ipc_list_resume_all(&(sem->parent.suspend_thread));
    //唤醒挂起线程(为使用信号量排队的线程),让他们返回失败(别等了,停车场关门了)

    /* detach semaphore object */
    rt_object_detach(&(sem->parent.parent));//信号量对象从内核对象管理器中脱离

    return RT_EOK;
}
RTM_EXPORT(rt_sem_detach);

3.创建和删除

创建
和初始化差别:没有已存在的信号量句柄,要自己创建,要自己给内存

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)
{
    rt_sem_t sem;//定义信号量句柄

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* allocate object */
    sem = (rt_sem_t)rt_object_allocate(RT_Object_Class_Semaphore, name);
    //对象分配内存
    if (sem == RT_NULL)//分配失败返回
        return sem;

    /* init ipc object */
    rt_ipc_object_init(&(sem->parent));
    //sem->parent的初始化(见1.2信号量控制块)

    /* set init value */
    sem->value = value;//赋值

    /* set parent */
    sem->parent.parent.flag = flag;//设置状态

    return sem;
}
RTM_EXPORT(rt_sem_create);

删除
和脱离差别:要自己释放内存

rt_err_t rt_sem_delete(rt_sem_t sem)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
    //rt_object名字是否为信号量并返回对象类型
    RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent) == RT_FALSE);
    //判断对象是否为系统对象
    /* wakeup all suspend threads */
    rt_ipc_list_resume_all(&(sem->parent.suspend_thread));
    //唤醒挂起线程(为使用信号量排队的线程),让他们返回失败(别等了,停车场关门了)

    /* delete semaphore object */
    rt_object_delete(&(sem->parent.parent));//释放内存

    return RT_EOK;
}
RTM_EXPORT(rt_sem_delete);
#endif

3.获取信号量

申请信号量,信号量无余值则定时等待,不等待返回错误

rt_err_t rt_sem_take(rt_sem_t sem, //信号量对象的句柄(停车场地址)
					 rt_int32_t time)//指定的等待时间(等待时间)
{
    register rt_base_t temp;//中断标志
    struct rt_thread *thread;//线程指针

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
      //rt_object名字是否为信号量并返回对象类型
    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent)));

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();//关中断1

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s take sem:%s, which value is: %d\n",
                                rt_thread_self()->name,
                                ((struct rt_object *)sem)->name,
                                sem->value));
//打印日志
    if (sem->value > 0)//有车位
    {
        /* semaphore is available */
        sem->value --;//车位减一

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);//开中断1
    }
    else
    {
        /* no waiting, return with timeout */
        if (time == 0)//(重点)这是定时器到了(再次申请车位),没等到车位,不等了,返回错误
        {
            rt_hw_interrupt_enable(temp);//开中断1

            return -RT_ETIMEOUT;//超时依然未获得信号量
        }
        else
        {
            /* current context checking */
            RT_DEBUG_IN_THREAD_CONTEXT;

            /* semaphore is unavailable, push to suspend list */
            /* get current thread */
            thread = rt_thread_self();//读取当前线程

            /* reset thread error number */
            thread->error = RT_EOK;//设置线程错误码为ok

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\n",
                                        thread->name));

            /* suspend thread */
            rt_ipc_list_suspend(&(sem->parent.suspend_thread),
                                thread,
                                sem->parent.parent.flag);
                                //挂起线程,没车位了

            /* has waiting time, start thread timer */
            if (time > 0)//设置的定时器值为正
            {
                RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
                                            thread->name));

                /* reset the timeout of thread timer and start it */
                rt_timer_control(&(thread->thread_timer),
                                 RT_TIMER_CTRL_SET_TIME,
                                 &time);
                                 //设置等待时间
                rt_timer_start(&(thread->thread_timer));//启动定时器
            }

            /* enable interrupt */
            rt_hw_interrupt_enable(temp);//开中断1

            /* do schedule */
            rt_schedule();//运行调度器

            if (thread->error != RT_EOK)
            {
                return thread->error;
            }
        }
    }

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent)));

    return RT_EOK;
}
RTM_EXPORT(rt_sem_take);

无等待获取信号量
time为0的获取函数

rt_err_t rt_sem_trytake(rt_sem_t sem)
{
    return rt_sem_take(sem, 0);
}
RTM_EXPORT(rt_sem_trytake);

4.释放信号量

释放信号量可以唤醒挂起在该信号量上的线程
1.有挂起线程:唤醒线程
2.无挂起线程:信号量value加一

rt_err_t rt_sem_release(rt_sem_t sem)
{
    register rt_base_t temp;//中断标志位
    register rt_bool_t need_schedule;//调度标志位

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
     //rt_object名字是否为信号量并返回对象类型

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent)));

    need_schedule = RT_FALSE;

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();//关中断1

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s releases sem:%s, which value is: %d\n",
                                rt_thread_self()->name,
                                ((struct rt_object *)sem)->name,
                                sem->value));
                                //日志记录

    if (!rt_list_isempty(&sem->parent.suspend_thread))//有挂起线程,唤醒挂起线程,调度标志位为true
    {
        /* resume the suspended thread */
        rt_ipc_list_resume(&(sem->parent.suspend_thread));
        need_schedule = RT_TRUE;
    }
    else
        sem->value ++; /* increase value */

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);//开中断

    /* resume a thread, re-schedule */
    if (need_schedule == RT_TRUE)//如果唤醒了线程,调度标志位为true,就启动调度器
        rt_schedule();

    return RT_EOK;
}
RTM_EXPORT(rt_sem_release);

信号量使用例子

该例程创建了一个动态信号量,初始化两个线程,一个线程发送信号量,一个线程接收到信号量后,执行相应的操作。

#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);

结果

 \ | /
- RT -     Thread Operating System
 / | \     3.1.0 build Aug 27 2018
 2006 - 2018 Copyright by rt-thread team
msh >semaphore_sample
create done. dynamic semaphore value = 0.
msh >t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 1
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 2
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 3
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 4
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 5
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 6
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 7
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 8
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 9
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 10

如上面运行结果:线程 1 在 count 计数为 10 的倍数时(count 计数为 100 之后线程退出),发送一个信号量,线程 2 在接收信号量后,对 number 进行加 1 操作。

信号量的另一个应用例程如下所示,本例程将使用 2 个线程、3 个信号量实现生产者与消费者的例子。其中:

3 个信号量分别为:①lock:信号量锁的作用,因为 2 个线程都会对同一个数组 array 进行操作,所以该数组是一个共享资源,锁用来保护这个共享资源。②empty:空位个数,初始化为 5 个空位。③full:满位个数,初始化为 0 个满位。

2 个线程分别为:①生产者线程:获取到空位后,产生一个数字,循环放入数组中,然后释放一个满位。②消费者线程:获取到满位后,读取数组内容并相加,然后释放一个空位。

本例子引用于官网文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值