RT-Thread--线程间同步--信号量 by:米醋电子工作室

信号量(sem)

信号量可以理解为电影院的座位数(资源数量),当所有座位都满了,新的观众(线程)就需要等待前面的观众离开(释放资源)才能进入。信号量通过控制资源数量来管理多个线程对资源的访问。

信号量的工作机制

信号量的核心思想是控制资源的访问数量。信号量的值代表了可以访问的资源数量。
(1)获取信号量(rt_sem_take):当资源可用时,线程可以获取信号量,信号量的值减1。如果信号量的值为0,线程就会等待,直到资源释放。
(2)释放信号量(rt_sem_release):使用完资源后,线程释放信号量,信号量的值加1,允许其他线程访问资源。

信号量应用实例

#include <rtthread.h>

//创建信号量
rt_thread_t sem = RT_NULL;

//线程1:等待进入电影院
void sem_thread_entry1(void *parameter)
{
  rt_kprintf("线程1:等待进入电影院\n");
  /*
      参数RT_WAITING_FOREVER意为无线等待
      作用是永久阻塞,直到获取信号量或被终止
  */
  rt_sem_take(sem,1,RT_WAITING_FOREVER);//尝试获取信号量
  rt_kprintf("线程1:进入电影院\n");
}

//线程2:离开电影院
void sem_thread_entry2(void *parameter)
{
  rt_kprintf("线程2:离开电影院\n");
  rt_sem_release(sem);//释放信号量
}

int main(void)
{
  //初始化信号量,初始值为1,表示电影院有一个座位
  sem = rt_sem_create("sem",1,RT_IPC_FLAG_PRID);
  
  //创建两个线程
  rt_thread_t tid1 = rt_thread_create("sem_tid1",sem_thread_entry1,RT_NULL,1024,25,10);
  rt_thread_t tid2 = rt_thread_create("sem_tid2",sem_thread_entry2,RT_NULL,1024,25,10);
  
  //启动线程
  rt_thread_startup(sem_tid1);  
  rt_thread_startup(sem_tid2);
  
  return 0;
}

解释:
sem_thread_entry1:线程等待进入电影院,需要等到有空座位(信号量>0)。
sem_thread_entry2:线程模拟一个观众离开电影院,释放一个座位,其他等待的线程可以进入。

相关API

1.rt_sem_create:用于动态创建信号量

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);

参数说明:
name:信号量的名字(可以为RT_NULL表示匿名信号量)
value:信号量的初值,表示信号量当前持有的资源数量(例如初始值为0表示等待事件,为1表示互斥量,或者更大值表示资源计数)
flag:IPC对象的属性标志,常用值:
RT_IPC_FLAG_PRID:优先级等待方式,等待线程按照优先级顺序排列
RT_IPC_FLAG_FIFO:先入先出等待方式,等待线程按照进入顺序排列
返回值:
成功:返回信号量的句柄rt_sem_t
失败:返回RT_NULL
示例:

//创建一个初始值为0的信号量,用于同步场景
rt_sem_t sync_sem = rt_sem_create("sync_sem",0,RT_IPC_FLAG_PRID);

2.rt_sem_init:用于静态信号量的初始化,一般用于静态分配内存的场景

rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value,rt_uint8_t flag);

参数说明:
sem:信号量对象的指针(需要预先定义好该信号量结构体)
name:信号量的名字
value:信号量的初值
flag:IPC对象的属性标志,如rt_sem_create
返回值:
成功:返回RT_EOK
失败:返回相应的错误代码
示例:

//静态信号量定义
static struct rt_semaphore static_sem;

//静态信号量初始化,初始值为1,用于互斥操作
rt_sem_init(&static_sem,"static_sem",1,RT_IPC_FLAG_PRID);

3.rt_sem_take:用于获取信号量,线程会尝试获取信号量,如果信号量的计数值为0,线程会进入等待状态,直到信号量被释放或超时。

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);

参数说明:
sem:信号量的句柄
time:超时时间(单位为系统tick)表示最大等待时间,如果设置为RT_WAITING_FOREVER,系统会一直等待
返回值:
成功:返回RT_EOK
失败:返回-RT_ETIMEOUT(超时)或其他错误代码
示例:

//线程1等待信号量,直到收到信号
rt_sem_take(sem_proc,RT_WAITING_FOREVER);

4.rt_sem_release:用于释放信号量,增加信号量的计数值,并唤醒等待该信号量的线程

rt_err_t rt_sem_release(rt_sem_t sem);

参数说明:
sem:信号量的句柄
返回值:
成功:返回RT_EOK
失败:返回相应的错误代码
示例:

//线程2完成任务后释放信号量,通知线程1
rt_sem_release(sync_sem);

5.rt_sem_delete:用于删除动态信号量(仅限于rt_sem_create创建的动态信号量)

rt_err_t rt_sem_delete(rt_sem_t sem);

参数说明:
sem:信号量的句柄
返回值:
成功:返回RT_EOK
错误:返回相应的错误代码
示例:

//删除动态创建的信号量
rt_sem_delete(sync_sem);

开发场景

在RTT中,信号量(sem)是一种常见的同步机制,主要用于任务之间的同步与互斥,它可以通过信号量的计数机制来协调多个线程的运行,确保资源的安全访问,信号量的开发场景通常有以下几种:

一、任务同步

多个任务之间需要在某些时间点进行同步时,可以使用信号量。信号量可以用来实现线程间的等待和通知机制。例如,当一个任务完成某项工作后,发送一个信号量,通知等待该信号量的任务继续执行。

应用场景示例:

(1)一个任务处理传感器数据,处理完成后通知另一个任务进行数据发送。
(2)一个任务等待外部事件(如外部中断)发生,中断发生后释放信号量,任务获取信号量后继续执行。

任务同步示例:

假设有两个线程tid1和tid2,tid1需要等待tid2完成某些操作后再继续执行,使用信号量来实现同步。

#include <rtthread.h>

#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5

static rt_thread_t thread1_handle = RT_NULL;
static rt_thread_t thread2_handle = RT_NULL;
static rt_sem_t sync_sem = RT_NULL;

//线程1入口函数
static void thread1_entry(void *parameter)
{
  rt_kprintf("线程1;等待线程2发送信号...\n");
  rt_sem_take(sync_sem,RT_WAITING_FOREVER);
  rt_kprintf("线程1:收到线程2的信号,继续执行...\n");
}

//线程2入口函数
static void thread2_entry(void *parameter)
{
  rt_kprintf("执行一些操作...\n");
  //模拟耗时操作
  rt_thread_mdelay(2000);
  rt_thread("线程2:操作完成,发送信号给线程1...\n");
  //释放信号量,通知线程1
  rt_sem_release(sync_sem);
}

int main(void)
{
  //创建一个信号量,初始值为0
  sync_sem = rt_sem_create("sync_sem",0,RT_IPC_FLAG_PRID);
  
  if(sync_sem == RT_NULL)
  {
     rt_kprintf("信号量创建失败\n");
     return -1;
  }

  //创建线程1
  thread1_handle = rt_thread_create("thread1",
                                     thread1_entry,
                                     RT_NULL,
                                     THREAD_STACK_SIZE,
                                     THREAD_PRIOITY,
                                     THREAD_TIMESLICE);
  if(thread1_handle != RT_NULL)
      rt_thread_startup(thread1_handle);

  //创建线程2
  thread2_handle = rt_thread_create("thread2",
                                     thread1_entry,
                                     RT_NULL,
                                     THREAD_STACK_SIZE,
                                     THREAD_PRIOITY,
                                     THREAD_TIMESLICE);
  if(thread2_handle != RT_NULL)
      rt_thread_startup(thread2_handle);

  return 0;
}

二、任务互斥

当多个任务需要访问共享资源时,可以使用信号量来实现互斥访问。互斥信号量(mutex)可以保证同一时刻只有一个任务能够访问共享资源,避免出现竞争条件和数据不一致的情况。

应用场景示例:

(1)多个任务访问同一个硬件外设(如串口、I2C总线)时,通过信号量保证同一时刻只有一个任务访问外设,防止访问冲突。
(2)多个任务需要访问同一块内存数据时,使用信号量确保数据访问的独占性

任务互斥示例:

当两个线程需要互斥访问共享资源时,可以使用信号量来确保同一时刻只有一个线程能够访问资源

#include <rtthread.h>

#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5

static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_sem_t mutex_sem = RT_NULL;

//模拟共享资源
static int shared_resource = 0;

//线程1入口函数
static void thread1_entry(void *parameter)
{
  while(1)
  {
    //获取互斥信号量
    rt_sem_take(mutex_sem, RT_WAITING_FOREVER);
    rt_kprintf("线程1:访问共享资源,当前值为:%d\n", shared_resource);
    shared_resource++;
    rt_kprintf("线程1:修改共享资源,值变为:%d\n", shared_resource);

    //模拟资源处理耗时
    rt_thread_mdelay(1000);

    //释放互斥信号量
    rt_sem_release(mutex_sem);

    //让出CPU给其他线程
    rt_thread_mdelay(500);
  }
}

//线程2入口函数
static void thread2_entry(void *parameter)
{
  while(1)
  {
    //获取互斥信号量
    rt_sem_take(mutex_sem, RT_WAITING_FOREVER);
    rt_kprintf("线程2:访问共享资源,当前值为:%d\n", shared_resource);
    shared_resource++;
    rt_kprintf("线程2:修改共享资源,值变为:%d\n", shared_resource);

    //模拟资源处理耗时
    rt_thread_mdelay(1000);

    //释放互斥信号量
    rt_sem_release(mutex_sem);

    //让出CPU给其他线程
    rt_thread_mdelay(500);
  }
}

int main(void)
{
  //创建一个互斥信号量,初始值为1
  mutex_sem = rt_sem_create("mutex_sem", 1, RT_IPC_FLAG_FIFO);

  if(mutex_sem == RT_NULL)
  {
    rt_kprintf("创建互斥信号量失败\n");
    return -1;
  }

  //创建线程1
  tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
  if(tid1 != RT_NULL)
  {
    rt_thread_startup(tid1);
  }

  //创建线程2
  tid2 = rt_thread_create("thread2", thread2_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
  if(tid2 != RT_NULL)
  {
    rt_thread_startup(tid2);
  }

  return 0;
}

三、事件触发

某些外部事件发生后,需要通知任务执行相应的处理逻辑。这种场景下,外部中断或事件处理函数可以通过释放信号量来通知任务进行处理。任务会等待信号量,当信号量被释放时,任务被唤醒并开始执行。

应用场景示例:

(1)一个任务等待按键时间,当按键被按下时,中断处理程序释放信号量,任务获取信号量并执行按键响应逻辑。
(2)网络模块等待数据接收时间,当网络数据到达时,触发信号量,通知任务进行数据处理。

事件触发示例:

#include "rtthread.h"

#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5

static rt_thread_t thread_handle = RT_NULL;
static rt_sem_t event_sem = RT_NULL;

//模拟外部事件发生,例如按键中断
void external_event_handler(void)
{
  rt_kprintf("外部事件发生!发送信号量...\n");

  //释放信号量,通知等待线程
  rt_sem_release(event_sem);
}

//线程入口函数
static void thread_entry(void *parameter)
{
  while (1)
  {
    rt_kprintf("等待外部事件...\n");

    //等待外部事件的信号量
    rt_sem_take(event_sem, RT_WAITING_FOREVER);

    rt_kprintf("线程:收到外部事件信号,处理事件...\n");

    //模拟处理事件过程
    rt_thread_mdelay(1000);
  }
}

int main(void)
{
  //创建一个信号量,初始值为0
  event_sem = rt_sem_create("event_sem", 0, RT_IPC_FLAG_FIFO);
  if (event_sem == RT_NULL)
  {
    rt_kprintf("创建信号量失败!\n");
    return -1;
  }

  //创建线程
  thread_handle = rt_thread_create("thread", thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
  if (thread_handle != RT_NULL)
  {
    rt_thread_startup(thread_handle);
  }

  //模拟外部事件
  rt_thread_mdelay(2000);
  external_event_handler();

  return 0;
}

四、限量资源管理

信号量可以用来管理有限的资源,信号量的计数值可以表示可用资源的数量。当一个任务获取到资源时,信号量的计数值减少;当任务释放资源时,信号量的计数值增加。如果信号量的计数值为0,则任务必须等待资源可用。

应用场景示例:

(1)一个系统有多个任务需要访问一个有限的资源池(如线程池,内存池),信号量可以表示可用资源的数量,确保任务只能在资源可用时进行访问。
(2)限制并发访问的数量,比如同一时刻只能有固定数量的任务访问某个服务。

限量资源管理示例:

#include <rtthread.h>

#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5

static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_sem_t res_sem = RT_NULL;

//线程1入口函数
static void thread1_entry(void *parameter)
{
  while(1)
  {
    //尝试获取资源
    if(rt_sem_take(res_sem, RT_WAITING_FOREVER) == RT_EOK)
    {
      rt_kprintf("线程1:成功获取资源!\n");

      //模拟使用资源
      rt_thread_mdelay(2000);

      //释放资源
      rt_sem_release(res_sem);
      rt_kprintf("线程1:释放资源!\n");
    }
    //让出CPU给其他线程
    rt_thread_mdelay(500);
  }
}

//线程2入口函数
static void thread2_entry(void *parameter)
{
  while(1)
  {
    //尝试获取资源
    if(rt_sem_take(res_sem, RT_WAITING_FOREVER) == RT_EOK)
    {
      rt_kprintf("线程2:成功获取资源!\n");

      //模拟使用资源
      rt_thread_mdelay(2000);

      //释放资源
      rt_sem_release(res_sem);
      rt_kprintf("线程2:释放资源!\n");
    }
    //让出CPU给其他线程
    rt_thread_mdelay(500);
  }
}

int main(void)
{
  //创建一个信号量,初始值为1,表示有一个可用资源
  res_sem = rt_sem_create("res_sem", 1, RT_IPC_FLAG_FIFO);

  if(res_sem == RT_NULL)
  {
    rt_kprintf("创建信号量失败!\n");
    return -1;
  }

  //创建线程1
  tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
  if(tid1 != RT_NULL)
  {
    rt_thread_startup(tid1);
  }

  //创建线程2
  tid2 = rt_thread_create("thread2", thread2_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
  if(tid2 != RT_NULL)
  {
    rt_thread_startup(tid2);
  }

  return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值