同步是指多个任务之间存在依赖关系,一个任务需要等待另一个任务完成某个操作后,才能继续执行。
现实生活例子:
计算机中的同步
void 同事A_写报表(void) {
printf("A: 正在写报表...\n");
sleep(2); // 模拟报表书写时间
printf("A: 报表写完了,通知经理B\n");
报表完成 = 1; // 设置标志
}
void 经理B_汇报(void) {
while (报表完成 == 0) { // B必须等待A写完报表
printf("B: 等待A完成报表...\n");
sleep(1);
}
printf("B: 报表完成了,现在去向领导汇报\n");
}
经理B必须等同事A写完报表后,才能去汇报。
当同步通过忙等待(busy waiting)的方式来实现时 :
来看看下面的代码:
static int sum = 0;
static volatile int flagCalcEnd = 0;
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000000; i++)
sum++;
//printf("1");
flagCalcEnd = 1;
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
while (1)
{
if (flagCalcEnd)
printf("sum = %d\r\n", sum);
}
}
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
//task2注释前后进行对比
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
没注释xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
前:
注释后:
通过时间对比还是可以明显看出,注释到后task1的运行速度快了一倍,这是因为没有Task2Function
去和task1抢夺资源,毕竟task2在这里使用的并不是休眠
互斥是指多个任务在访问同一个共享资源时,必须互相排斥,即同一时间只能有一个任务使用该资源,其他任务必须等待。
现实生活例子:
计算机中的互斥
mutex_t 厕所锁; // 定义一个互斥锁
void 人A_上厕所(void) {
mutex_lock(&厕所锁); // A 进入厕所,锁定资源
printf("A: 我正在上厕所,B不能进来\n");
sleep(2); // 模拟上厕所时间
printf("A: 上完厕所,释放资源\n");
mutex_unlock(&厕所锁); // A 释放资源
}
void 人B_上厕所(void) {
mutex_lock(&厕所锁); // B 进入厕所,锁定资源
printf("B: 我正在上厕所,A不能进来\n");
sleep(2);
printf("B: 上完厕所,释放资源\n");
mutex_unlock(&厕所锁); // B 释放资源
}
当然上面的例子是通过锁来实现互斥的,如果使用一个全局变量来模拟实现互斥,也是会有缺陷的
static volatile int flagUARTused = 0;
void TaskGenericFunction(void * param)
{
while (1)
{
if (!flagUARTused)
{
flagUARTused = 1;
printf("%s\r\n", (char *)param);
flagUARTused = 0;
vTaskDelay(1);
}
}
}
//假设还有其它高优先级任务
//这个高优先级任务是有执行到taskDelay的,也就是休眠
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
//假设我还创建了一个高优先级任务
xTaskCreate(TaskGenericFunction, "Task1", 100, "Task 1 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task2", 100, "Task 2 is running", 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
在这个代码中使用的是用一个全局变量flagUARTused来实现互斥,公共资源是TaskGenericFunction
打印函数
task1和task2两个任务,假设在执行task1的时候,如果它在运行到TaskGenericFunction
函数中的flagUARTused = 1;
时,高优先级任务休眠结束,抢占了资源,之后结束运行了,task1没抢到反而被task2抢到了,运行打印函数时if (!flagUARTused)
反而成立了,访问到了公共资源。
但是之前的task1,还没访问到,这就违背了互斥,我task1明明先用到了,我打印一半就被别人抢去了,这还能叫互斥?
同步和互斥经常一起使用,因为互斥可以通过同步来实现。
例如,在上厕所的场景中:
mutex_t 厕所锁;
int 轮到谁 = 1; // 1代表A先上,2代表B上
void 人A_上厕所(void) {
while (轮到谁 != 1) { sleep(1); } // B正在用,A等待
mutex_lock(&厕所锁);
printf("A: 正在用厕所...\n");
sleep(2);
printf("A: 用完了,通知B\n");
轮到谁 = 2; // 轮到B上厕所
mutex_unlock(&厕所锁);
}
void 人B_上厕所(void) {
while (轮到谁 != 2) { sleep(1); } // A正在用,B等待
mutex_lock(&厕所锁);
printf("B: 正在用厕所...\n");
sleep(2);
printf("B: 用完了,通知A\n");
轮到谁 = 1; // 轮到A上厕所
mutex_unlock(&厕所锁);
}
mutex_lock()
和 mutex_unlock()
确保同时只有一个人能用厕所。轮到谁
变量,让A、B按顺序使用厕所,保证流程正确。概念 | 解释 | 现实例子 | 代码示例 |
---|---|---|---|
同步 | 任务间有依赖,必须按顺序执行 | 经理B必须等A写完报表才能汇报 | while(报表未完成) { 等待 } |
互斥 | 任务争抢资源,必须轮流使用 | 会议室一次只能开一个会 | mutex_lock(资源) |
能实现同步、互斥的内核方法有:
对象类型 | 生产者/消费者关系 | 能否传递数据/状态 | 主要用途 |
---|---|---|---|
队列 (Queue) | 发送者、接收者均无限制,多对多 | 可以传递任意数据,多个数据项 | 用于传递数据,任务或ISR都可以入队和出队;一个数据通常只能唤醒一个接收者 |
事件组 (Event Group) | 发送者、接收者均无限制,多对多 | 传递的是事件状态,每个位表示一个事件(1:发生,0:未发生) | 用于传递事件或者事件组合,有类似广播的效果,可唤醒多个等待任务 |
信号量 (Semaphore) | 发送者、接收者均无限制,多对多 | 传递计数值(范围0~n),仅反映资源数量 | 主要用来管理资源个数,获取信号量代表占用资源,释放信号量代表资源释放;一个资源唤醒一个等待者 |
任务通知 (Task Notification) | 发送者无限制,接收者只能是指定任务(一对多转为N对1) | 可以传递简单的数据或状态,但后续通知会覆盖之前的通知 | 轻量级的同步方式,必须指定接收任务,适合在任务之间传递状态或简单信息 |
互斥量 (Mutex) | 只能由同一任务先“上锁”,后“释放” | 仅能传递0和1的状态,反映锁定或空闲状态 | 用于保护临界资源,同一时间只有一个任务能访问;获取锁的任务必须自己释放 |
小提示:
这些内核对象都支持类似的操作:获取/释放、阻塞/唤醒、以及超时机制。例如:
队列:
事件组:
信号量:
任务通知:
互斥量:
所有这些内核对象都有如下共性操作:
如何选择合适的同步/互斥方法:
为什么变量i加上volatile可以让程序变慢一点?