基本概念

  • 同步是指按预定的先后次序进行运行
  • 在访问临界区的时候只允许一个 (或一类) 线程运行

进入 / 退出临界区的方式

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++;}elsereturn;/* 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 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 逻辑与逻辑或将一个或多个事件关联起来,形成事件组合。事件的 逻辑或也称为是独立型同步,指的是线程与任何事件之一发生同步;事件逻辑与 也称为是关联型同步,指的是线程与若干事件都发生同步。

事件集有以下特点:

  1. 事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
  2. 事件仅用于同步,不提供数据传输功能
  3. 事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

事件信息标记

每个线程都拥有一个事件信息标记,它有三个属性,分别是

  1. RT_EVENT_FLAG_AND(逻辑与),
  2. RT_EVENT_FLAG_OR(逻辑或)
  3. 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);

事件集的使用场合

  • 事件集可使用于多种场合,它能够在一定程度上替代信号量,用于线程间同步。
  • 一个线程或中断服务例程发送一个事件给事件集对象,而后等待的线程被唤醒并对相应的事件进行处理
  • 与信号量不同的是,事件的发送操作在事件未清除前,是不可累计的,而信号量的释放动作是累计的

RTT学习笔记4-线程同步相关推荐

  1. C#学习笔记之线程 - 同步上下文

    同步上下文(Synchronization Contexts) 手动使用锁的一个替代方案是去声明锁.通过派生ContextBoundObject和应用Synchronization属性,你告诉CLR自 ...

  2. 【Linux学习笔记】线程同步 之 信号量 之 sem_t结构体

    sem_t结构体: 里面封装了两个数据: 1.__align:long int 型,保存了信号量的值: 2.__size:char型,功能未知,常为空值,一般不需要使用; sem_t mutex;se ...

  3. VS2017 CUDA编程学习8:线程同步-原子操作

    文章目录 前言 1. 原子操作的理解 2. C++ CUDA实现原子操作 3. 执行结果 总结 学习资料 VS2017 CUDA编程学习1:CUDA编程两变量加法运算 VS2017 CUDA编程学习2 ...

  4. java学习笔记15--多线程编程基础2

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...

  5. 多人网络游戏服务器开发基础学习笔记 II: 帧同步 | 游戏客户端预测原理分析 | FPS 游戏状态同步

    这篇是对书本 网络多人游戏架构与编程 的学习第二篇(第一篇:多人网络游戏服务器开发基础学习笔记 I:基本知识 | 游戏设计模式 | 网游服务器层次结构 | 游戏对象序列化 | 游戏 RPC 框架 | ...

  6. java学习笔记14--多线程编程基础1

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...

  7. 关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)

    在多线程应用程序中经常会遇到线程同步的问题.比如:两个线程A.线程B可能会 "同时" 执行同一段代码,或修改同一个变量.而很多时候我们是不希望这样的. 这时候,就需要用到线程同步. ...

  8. 5G NR - RACH学习笔记2 - 上行同步/Timing Advance

    UE在获取下行同步(PSS/SSS)后, 需要通过RACH过程(随机接入过程)接入到基站(gNodeB),这个时候基站由于尚未与UE达到上行同步,因此首先需要执行上行同步. 那么怎么达到上行同步? - ...

  9. java笔记--关于线程同步(7种同步方式)

    关于线程同步(7种方式) --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3897440.html"谢谢-- 为何要使用同步? ...

最新文章

  1. 查看mysql主外键信息
  2. android休眠唤醒驱动流程分析【转】
  3. Wireshark抓包—maybe caused by 'IP chechsum offload'?
  4. XML解析之JAXP案例详解
  5. java ajax 定时刷新_用ajax技术实现无闪烁定时刷新页面
  6. Spring注解——使用@ComponentScan自动扫描组件
  7. 深度残差收缩网络:(三)网络结构
  8. 模仿vue自己动手写响应式框架( - v-for
  9. 看看什么样的人适合网上开店( 转载)
  10. Python+OpenCV:图像修复(Image Inpainting)
  11. SQL Server:如何加入第一行
  12. 对警报线程池的警报线程_使用警报控制器的iOS操作表
  13. java 开源进销存项目_JSH_ERP 开源版J2EE进销存系统代码源码下载|JSH_ERP 开源版J2EE进销存系统代码源码官方下载-太平洋下载中心...
  14. Phyton学习笔记
  15. 连个字体反爬都搞不定?你还说你会爬虫?看完这篇就会了。
  16. VB程序设计教程(第四版)龚沛曾 实验8-2
  17. 知识型IP与网红的区别
  18. c# MouseClick和MouseDown的区别
  19. USRP工作流程及各部分功能
  20. 联通短消息服务器域名,中国联通域名服务器地址

热门文章

  1. Matlab优化求解器中的Tolerances and Stopping Criteria
  2. 商淘多b2b2c商城系统怎么在个人电脑上安装_企业怎么做好b2b2c商城网站建设?...
  3. 电力装置的继电保护和自动装置设计规范_继电保护装置升级到微机保护装置
  4. 拼接 结果集_JUST技术:利用轨迹拼接分析实时可达区域|技术前沿
  5. fastapi学习(二):路径参数获取与GET、POST参数获取
  6. 推荐系统学习(三)SVD奇异值分解做推荐与python代码
  7. python保存与加载LGBM模型,并解决报错TypeError: Need at least one training dataset or model file or model string..
  8. pandas时间序列与自回归ARIMA模型
  9. pythonfor循环加2_python中for循环如何实现每次控制变量翻倍
  10. HTTP1.1/2.0与QUIC协议