C语言 Freertos的递归锁详解

目录
  • 1.死锁的概念
  • 2.自我死锁
  • 3.递归锁
  • 4.代码
  • 5.运行流程分析
  • 6.运行结果
  • 总结

1.死锁的概念

假设有 2 个互斥量 M1、 M2, 2 个任务 A、 B:
A 获得了互斥量 M1
B 获得了互斥量 M2
A 还要获得互斥量 M2 才能运行,结果 A 阻塞
B 还要获得互斥量 M1 才能运行,结果 B 阻塞
A、 B 都阻塞,再无法释放它们持有的互斥量
死锁发生!

2.自我死锁

任务 A 获得了互斥锁 M
它调用一个函数
函数要去获取同一个互斥锁 M,于是它阻塞:任务 A 休眠,等待任务 A
来释放互斥锁!
死锁发生!

3.递归锁

1.任务 A 获得递归锁 M 后,它还可以多次去获得这个锁

2."take"了 N 次,要"give"N 次,这个锁才会被释放

3.谁上锁就由谁解锁。

递归锁的函数根一般互斥量的函数名不一样

  递归锁 一般互斥量
创建 xSemaphoreCreateRecursiveMutex xSemaphoreCreateMutex
获得 xSemaphoreTakeRecursive xSemaphoreTake
释放 xSemaphoreGiveRecursive xSemaphoreGive

4.代码

main

/* 递归锁句柄 */
SemaphoreHandle_t xMutex;
int main( void )
{
	prvSetupHardware();
    /* 创建递归锁 */
    xMutex = xSemaphoreCreateRecursiveMutex( );
	if( xMutex != NULL )
	{
		/* 创建2个任务: 一个上锁, 另一个自己监守自盗(开别人的锁自己用)
		xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
		xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );
		/* 启动调度器 */
		vTaskStartScheduler();
	}
	else
	{
		/* 无法创建递归锁 */
	}
	return 0;
}

任务1

static void vTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
	BaseType_t xStatus;
	int i;

	/* 无限循环 */
	for( ;; )
	{
		/* 获得递归锁: 上锁 */
		xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
		printf("Task1 take the Mutex in main loop %s\r\n", \
			(xStatus == pdTRUE)? "Success" : "Failed");
		/* 阻塞很长时间, 让另一个任务执行,
		 * 看看它有无办法再次获得递归锁
		 */
		vTaskDelay(xTicksToWait);
		for (i = 0; i < 10; i++)
		{
			/* 获得递归锁: 上锁 */
			xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
			printf("Task1 take the Mutex in sub loop %s, for time %d\r\n", \
				(xStatus == pdTRUE)? "Success" : "Failed", i);
			/* 释放递归锁 */
			xSemaphoreGiveRecursive(xMutex);
		}
		/* 释放递归锁 */
		xSemaphoreGiveRecursive(xMutex);
	}
}

任务2

static void vGiveAndTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	BaseType_t xStatus;
	/* 尝试获得递归锁: 上锁 */
	xStatus = xSemaphoreTakeRecursive(xMutex, 0);
	printf("Task2: at first, take the Mutex %s\r\n", \
		(xStatus == pdTRUE)? "Success" : "Failed");
	/* 如果失败则监守自盗: 开锁 */
	if (xStatus != pdTRUE)
	{
		/* 无法释放别人持有的锁 */
		xStatus = xSemaphoreGiveRecursive(xMutex);
		printf("Task2: give Mutex %s\r\n", \
			(xStatus == pdTRUE)? "Success" : "Failed");
	}
	/* 如果无法获得, 一直等待 */
	xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
	printf("Task2: and then, take the Mutex %s\r\n", \
		(xStatus == pdTRUE)? "Success" : "Failed");
	/* 无限循环 */
	for( ;; )
	{
		/* 什么都不做 */
		vTaskDelay(xTicksToWait);
	}
}

5.运行流程分析

1.任务 1 优先级最高,先运行,获得递归锁

2.任务 1 阻塞,让任务 2 得以运行

3.任务 2 运行,看看能否获得别人持有的递归锁: 不能

4.任务 2 故意执行"give"操作,看看能否释放别人持有的递归锁:不能

5.任务 2 等待递归锁

6.任务 1 阻塞时间到后继续运行,使用循环多次获得、释放递归锁

6.运行结果

总结

谁持有递归锁,必须由谁释放。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C++11如何实现无锁队列

    无锁操作的本质依赖的原子操作,C++11提供了atomic的原子操作支持 atomic compare_exchange_weak / compare_exchange_strong 当前值与期望值相等时,修改当前值为设定值,返回true 当前值与期望值不等时,将期望值修改为当前值,返回false memory_order枚举值 template<typename T> class lock_free_stack { private: struct node { T data; node* n

  • C++线程中几类锁的详解

    目录 C++线程中的几类锁 互斥锁 条件锁 自旋锁 读写锁 参考博客 总结 C++线程中的几类锁 多线程中的锁主要有五类:互斥锁.条件锁.自旋锁.读写锁.递归锁.一般而言,所得功能与性能成反比.而且我们一般不使用递归锁(C++提供std::recursive_mutex),这里不做介绍. 互斥锁 ==互斥锁用于控制多个线程对它们之间共享资源互斥访问的一个信号量.==也就是说为了避免多个线程在某一时刻同时操作一个共享资源,例如一个全局变量,任何一个线程都要使用初始锁互斥地访问,以避免多个线程同时访

  • 浅谈C++11中的几种锁

    目录 互斥锁(mutex) 条件锁(condition_variable) 自旋锁(不推荐使用) 递归锁(recursive_mutex) 互斥锁(mutex) 可以避免多个线程在某一时刻同时操作一个共享资源,标准C++库提供了std::unique_lock类模板,实现了互斥锁的RAII惯用语法:eg: std::unique_lock<std::mutex> lk(mtx_sync_); 条件锁(condition_variable) 条件锁就是所谓的条件变量,某一个线程因为某个条件未满足

  • C++多线程之互斥锁与死锁

    目录 1.前言 2.互斥锁 2.1 互斥锁的特点 2.2 互斥锁的使用 2.3 std::lock_guard 3.死锁 3.1 死锁的含义 3.2 死锁的例子 3.3 死锁的解决方法 1.前言 比如说我们现在以一个list容器来模仿一个消息队列,当消息来临时插入list的尾部,当读取消息时就把头部的消息读出来并且删除这条消息.在代码中就以两个线程分别实现消息写入和消息读取的功能,如下: class msgList { private: list<int>mylist; //用list模仿一个

  • C++11各种锁的具体使用

    目录 Mutex(互斥锁) 什么是互斥量(锁)? 条件变量condition_variable: condition_variable的wait std::shared_mutex 原子操作 Mutex(互斥锁) 什么是互斥量(锁)? 这样比喻:单位上有一台打印机(共享数据a),你要用打印机(线程1要操作数据a),同事老王也要用打印机(线程2也要操作数据a),但是打印机同一时间只能给一个人用,此时,规定不管是谁,在用打印机之前都要向领导申请许可证(lock),用完后再向领导归还许可证(unloc

  • C语言 Freertos的递归锁详解

    目录 1.死锁的概念 2.自我死锁 3.递归锁 4.代码 5.运行流程分析 6.运行结果 总结 1.死锁的概念 假设有 2 个互斥量 M1. M2, 2 个任务 A. B: A 获得了互斥量 M1 B 获得了互斥量 M2 A 还要获得互斥量 M2 才能运行,结果 A 阻塞 B 还要获得互斥量 M1 才能运行,结果 B 阻塞 A. B 都阻塞,再无法释放它们持有的互斥量 死锁发生! 2.自我死锁 任务 A 获得了互斥锁 M 它调用一个函数 函数要去获取同一个互斥锁 M,于是它阻塞:任务 A 休眠,

  • 详解Python中的GIL(全局解释器锁)详解及解决GIL的几种方案

    先看一道GIL面试题: 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因. GIL:又叫全局解释器锁,每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题.它并不是python语言的特性,仅仅是由于历史的原因在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中. 通过

  • C语言 链式二叉树结构详解原理

    目录 前言 二叉树节点声明 二叉树的遍历 构建二叉树 1.前序遍历 2.中序遍历 3.后序遍历 二叉树节点的个数 二叉树叶子节点的个数 二叉树第K层节点个数 二叉树的高度/深度 二叉树查找值为x的节点 整体代码 前言 二叉树不同于顺序表,一颗普通的二叉树是没有增删改查的意义.普通的二叉树用来存储数据是不方便的.但是二叉树的一些基本实现结构,例如前序遍历,中序遍历...等等都是对我们学习更深层次的二叉树打下夯实的基础. 二叉树节点声明 typedef char BTDataType; typede

  • C语言程序环境和预处理详解分析

    目录 一.程序的翻译环境和运行环境 程序的翻译环境 链接阶段 执行环境(运行环境) 二.预处理详解 预定义符号 #define定义标识符 #define定义宏 #define 替换规则 #和##两个预处理的工具 带副作用的宏参数 宏和函数对比 #undef移除宏 命令行定义 条件编译 头文件包含 嵌套文件包含 总结 一.程序的翻译环境和运行环境 重点:任何ANSI C(标准C的程序)的一种实现,存在两个不同的环境 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令. 第2种是执行环境,

  • C语言 模拟实现strlen函数详解

    目录 前言 一.strlen函数的介绍 1.strlen函数的声明 2.strlen函数的简单运用 3.注意事项 二.三种实现strlen函数的方法 1.计数器的方法 2.递归方法 3.指针-指针的方法 前言 用C语言模拟实现strlen函数,我这里有三种方法,快来看看跟你用的方法是否是一样. 一.strlen函数的介绍 1.strlen函数的声明 size_t strlen ( const char * str ): 这里函数的返回值为无符号整形(size_t),传入的是一个常量char*类型

  • Python学习之线程池与GIL全局锁详解

    目录 线程池 线程池的创建 - concurrent 线程池的常用方法 线程池演示案例 线程锁 利用线程池实现抽奖小案例 GIL全局锁 GIL 的作用 线程池 线程池的创建 - concurrent concurrent 是 Python 的内置包,使用它可以帮助我们完成创建线程池的任务. 方法名 介绍 示例 futures.ThreadPoolExecutor 创建线程池 tpool=ThreadPoolExecutor(max_workers) 通过调用 concurrent 包的 futu

  • C语言算法学习之双向链表详解

    目录 一.练习题目 二.算法思路 1.设计浏览器历史记录 2.扁平化多级双向链表 3.展平多级双向链表 4.二叉搜索树与双向链表 一.练习题目 题目链接 难度 1472. 设计浏览器历史记录 ★★★☆☆ 430. 扁平化多级双向链表 ★★★☆☆ 剑指 Offer II 028. 展平多级双向链表 ★★★☆☆ 剑指 Offer 36. 二叉搜索树与双向链表 ★★★★☆ 二.算法思路 1.设计浏览器历史记录 1.这是一个模拟题: 2.初始化生成一个头结点,记录一个当前结点: 3.向前 和 向后 是两

  • Go语言包和包管理详解

    目录 1 包简介 1.1 工作空间 1.2 源文件 1.3 包命名 1.4 main 包 2导包 2.1 两种方式 2.2 包的别名 2.3 简洁模式 2.4非导入模式(匿名导入) 2.5 导包的路径 2.6 远程导入 3 初始化 init 3.1 init总结 4 包管理 4.1 演变过程 4.2 Go Model优点 4.3 启用go module 4.4 GOPROXY 5 go mod详解 5.1 go mod命令 5.2 go.mod说明 5.2.1 依赖的版本 5.2.2 repla

  • Go语言数据结构之二叉树可视化详解

    目录 题目 源代码 做题思路 扩展 左右并列展示 上下并列展示 总结回顾 题目 以图形展示任意二叉树,如下图,一个中缀表达式表示的二叉树:3.14*r²*h/3 源代码 package main import ( "fmt" "io" "os" "os/exec" "strconv" "strings" ) type any = interface{} type btNode struc

  • go语言编程实现递归函数示例详解

    目录 前言 函数中的 return 递归的问题 总结 前言 本篇文章主要是记录一下在 GScript 中实现递归调用时所遇到的坑,类似的问题在中文互联网上我几乎没有找到相关的内容,所以还是很有必要记录一下. 在开始之前还是简单介绍下本次更新的 GScript v0.0.9 所包含的内容: 支持可变参数 优化 append 函数语义 优化编译错误信息 最后一个就是支持递归调用 先看第一个可变参数: //formats according to a format specifier and writ

随机推荐