同步互斥
1. 同步
同步指的是协调多个线程或进程的执行顺序,以确保某些操作按预期的顺序进行。同步主要用于解决依赖关系的问题,确保线程之间的协调。
- 目的: 保证操作的顺序,确保某些条件成立前不进行后续操作。
- 实现方式:
- 信号量: 控制访问共享资源的数量,可以限制同时访问的线程数。
- 条件变量: 允许线程在某个条件满足之前等待,适用于生产者-消费者问题。
- 屏障: 让一组线程在某个点上同步,确保所有线程在继续之前都到达该点。
2. 互斥
互斥(Mutual Exclusion)指的是在同一时间只允许一个线程或进程访问某个共享资源,以避免数据冲突或损坏。
- 目的: 确保在任何时刻只有一个线程可以访问共享资源,保护数据的完整性。
- 实现方式:
- 互斥锁(Mutex): 一种简单的锁机制,当一个线程获得锁时,其他线程必须等待,直到锁被释放。
- 读写锁: 允许多个线程同时读,但在写时只允许一个线程写,适合读多写少的场景。
- 自旋锁: 在获取锁时,如果锁不可用,线程会忙等待,适用于短时间的锁争用。
总结
- 同步主要关注操作的顺序和条件,确保各线程按预期顺序执行。
- 互斥关注资源的独占访问,确保同一时间只有一个线程可以操作共享资源。
这两者结合使用,可以有效地管理多线程环境中的资源访问和执行顺序,从而提高程序的稳定性和性能。
一句话理解同步与互斥:我等你用完厕所,我再用厕所。 什么叫同步?就是:哎哎哎,我正在用厕所,你等会。 什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。 同步与互斥经常放在一起讲,是因为它们之的关系很大, “ 互斥 ” 操作可以使用 “ 同步 ” 来 实现。我 “ 等 ” 你用完厕所,我再用厕所。这不就是用 “ 同步 ” 来实现 “ 互斥 ” 吗? 再举一个例子。在团队活动里,同事 A 先写完报表,经理 B 才能拿去向领导汇报。经理 B 必须等同事 A 完成报表, AB 之间有依赖, B 必须放慢脚步,被称为同步。在团队活动中,同 事 A 已经使用会议室了,经理 B 也想使用,即使经理 B 是领导,他也得等着,这就叫互斥。经 理B跟同事A 说:你用完会议室就提醒我。这就是使用 " 同步 " 来实现 " 互斥 " 。 有时候看代码更容易理解,伪代码如下假设有 A 、 B 两人早起抢厕所, A 先行一步占用了; B 慢了一步,于是就眯一会;当 A 用 完后叫醒 B , B 也就愉快地上厕所了。 在这个过程中, A 、 B 是互斥地访问 “ 厕所 ” , “ 厕所 ” 被称之为临界资源。我们使用了 “ 休 眠 - 唤醒 ” 的同步机制实现了 “ 临界资源 ” 的 “ 互斥访问 ” 。 同一时间只能有一个人使用的资源,被称为临界资源。比如任务 A 、 B 都要使用串口来 打印,串口就是临界资源。如果 A 、 B 同时使用串口,那么打印出来的信息就是 A 、 B 混杂, 无法分辨。所以使用串口时,应该是这样: A 用完, B 再用; B 用完, A 再用void 抢厕所(void) { if (有人在用) 我眯一会; 用厕所; 喂,醒醒,有人要用厕所吗; }
有缺陷的同步示例
A任务
struct TaskPrintInfo {
uint8_t x;
uint8_t y;
char name[16];
};
static struct TaskPrintInfo g_Task1Info = {0, 0, "Task1"};
static struct TaskPrintInfo g_Task2Info = {0, 3, "Task2"};
static struct TaskPrintInfo g_Task3Info = {0, 6, "Task3"};
static int g_LCDCanUse = 1;
static volatile int g_calc_end=0;
static int g_time = 0;
static int g_sum = 0;
void CalcTask(void *params){
uint32_t i=0;
g_time=system_get_ns();
for(i=0;i<10000000;i++){
g_sum+=i;
}
g_calc_end=1;
g_time=system_get_ns()-g_time;
vTaskDelete(NULL);
}
B任务
void LcdPrintTask(void *params)
{
int len;
while (1)
{
LCD_PrintString(0,0,"Waiting");
vTaskDelay(3000);
while(g_calc_end==0){
}
/* ´òÓ¡ÐÅÏ¢ */
if (g_LCDCanUse)
{
g_LCDCanUse = 0;
LCD_ClearLine(0,0);
len = LCD_PrintString(0, 0,"Sum:");
LCD_PrintHex(len, 0, g_sum,1 );
LCD_ClearLine(0,2);
len=LCD_PrintString(0,2,"Time(ns)");
LCD_PrintSignedVal(len,2,g_time/1000000);
g_LCDCanUse = 1;
}
vTaskDelete(NULL);
}
}
这样就可以实现同步的任务运行
有缺陷的互斥示例
错误的解决
第一个错误方法思路
在裸机程序里,可以使用一个全局变量或静态变量实现互斥操作,比如要互斥地使用 LCD,可以使用如下代码:但是在 RTOS 里,使用上述代码实现互斥操作时,大概率是没问题的,但是无法确保万无一失。 假设如下场景:有两个任务 A、B 都想调用 LCD_PrintString,任务 A 执行到第 4 行代 码时发现 bCanUse 为 1,可以进入 if 语句块,它还没执行第 6 句指令就被切换出去了;然 后任务 B 也调用 LCD_PrintString,任务 B 执行到第 4 行代码时也发现 bCanUse 为 1,也可 以进入 if 语句块使用 LCD。在这种情况下,使用静态变量并不能实现互斥操作。int LCD_PrintString(int x, int y, char *str) { static int bCanUse = 1; if (bCanUse) { bCanUse = 0; /* 使用LCD */ bCanUse = 1; return 0; } return -1; }
第二个错误方法思路
上述例子中,是因为第 4、第 6 两条指令被打断了,那么如下改进:在函数入口处先然 让 bCanUse 减一。这能否实现万无一失的互斥操作呢?int LCD_PrintString(int x, int y, char *str)
{
static int bCanUse = 1;
bCanUse--;
if (bCanUse == 0)
{
/* 使用LCD */
bCanUse++;
return 0;
}
else
{
bCanUse++;
return -1;
}
}
把第 4 行的代码使用汇编指令表示如下: 分为三个步骤假设如下场景:有两个任务 A、B 都想调用 LCD_PrintString,任务 A 执行到第 04.1 行 代码时读到的 bCanUse 为 1,存入寄存器 R0 就被切换出去了;然后任务 B 也调用 LCD_PrintString,任务 B 执行到第 4 行时发现 bCanUse 为 1 并把它减为 0,执行到第 5 行 代码时发现条件成立可以进入 if 语句块使用 LCD,然后任务 B 也被切换出去了;现在任务 A 继续运行第 04.2 行代码时 R0 为 1,运行到第 04.3 行代码时把 bCanUse 设置为 0,后续也 能成功进入 if 的语句块。在这种情况下,任务 A、B 都能使用 LCD。 其中static是所有访问这个函数的任务都可以使用的,但是以上进入Tick中断的情况是发生在04.1与04.2之间那么bCanUse的值已经传入寄存器R0 所以这才可以保证任务同时开启04.1 LDR R0, [bCanUse] // 读取bCanUse的值,存入寄存器R0 04.2 DEC R0, #1 // 把R0的值减一 04.3 STR R0, [bCanUse] // 把R0写入变量bCanUse
目前优解(但是没有解决资源浪费的问题)
这是通过关闭中断,来解决不知何时进入中断所导致的互斥问题
int LCD_PrintString(int x, int y, char *str) {
static int bCanUse = 1;
disable_irq();
if (bCanUse)
{
bCanUse = 0;
enable_irq();
/* 使用LCD */
bCanUse = 1;
return 0;
}
enable_irq();
return -1;
}
标签:同步,示例,int,bCanUse,互斥,LCD,线程,缺陷
From: https://blog.csdn.net/2302_79504723/article/details/143243819