[嵌入式系统-20]:RT-Thread -6- 内核组件编程接口 - 线程编与线程间同步、互斥:信号量、互斥锁

目录

一、线程管理(Thread)

二、信号量(Semaphore)

2.1 概述

2.2 rt_sem_create 和 rt_sem_init的区别

三、互斥锁(Mutex)


一、线程管理(Thread)

线程(Thread)是进程中的执行单元,是程序中独立的、可调度的执行流程。一个进程可以包含多个线程,各个线程之间共享进程的资源,包括内存空间、文件描述符等。与进程相比,线程具有轻量级、创建和销毁开销小、切换速度快等优势。

线程可以并发执行,即多个线程同时运行在多个 CPU 核心上,提高了程序的并发性和多任务处理能力。多线程的运行可以让程序在进行输入输出等等待操作时,不必阻塞整个程序,而可以继续执行其他任务,提高了程序的效率和响应性。

线程之间可以通过共享内存进行通信,但相应地也需要采取同步机制(如信号量、互斥锁等)来避免数据竞争和资源冲突的问题。

在实际编程中,可以使用多种编程语言来创建和管理线程,如C、C++、Java、Python等。不同的编程语言提供了不同的线程库和方式来创建和控制线程,如pthread(POSIX 线程库)在 C 语言中,Java 中的线程类和方法(Thread、Runnable 接口等),Python 中的 threading 模块等。

线程的使用可以帮助程序实现并发处理、异步操作、并行计算等,但也需要注意线程安全性、资源管理、死锁等问题。合理地使用线程,可以充分发挥多核处理器的性能,提高程序的效率和质量。

在 RT-Thread 中,线程是一种并发执行的基本单位。RT-Thread 实时操作系统支持多线程任务,并且可以根据任务的优先级来进行调度。每个线程都有自己的线程控制块(TCB),用于保存线程的上下文信息和状态。

RT-Thread 中创建线程的方法有两种:

  1. 静态创建线程:静态创建线程通过在源代码中直接定义线程对象的方式来创建。静态创建线程需要在编译时确定线程数量和优先级,并且需要预先分配好线程的栈空间。这种方式适合线程数量和优先级固定不变的场景。

    静态创建线程的示例代码如下:

    #include <rtthread.h>static rt_thread_t tid;
    static char thread_stack[512];static void thread_entry(void* parameter)
    {// 线程处理逻辑
    }int app_mysample_main(void)
    {tid = rt_thread_create("my_thread", thread_entry, RT_NULL, sizeof(thread_stack), RT_THREAD_PRIORITY_MAX / 2, 20);if (tid != RT_NULL){rt_thread_startup(tid);}else{rt_kprintf("Failed to create thread.\n");return -1;}return 0;
    }
    

    在上述示例中,我们先定义了一个线程对象 tid 和一个线程栈空间 thread_stack,然后通过 rt_thread_create() 函数创建线程,并指定线程名称、线程入口函数、线程栈空间的大小、线程优先级等信息。

  2. 动态创建线程:动态创建线程通过在运行时动态申请内存来创建线程对象。动态创建线程的方式更加灵活,可以在运行时根据需要动态创建和销毁线程。

    动态创建线程的示例代码如下:

    #include <rtthread.h>static rt_thread_t tid;static void thread_entry(void* parameter)
    {// 线程处理逻辑
    }int app_mysample_main(void)
    {tid = rt_thread_create_dyn("my_thread", RT_THREAD_PRIORITY_MAX / 2, 2048, 20, thread_entry, RT_NULL);if (tid != RT_NULL){rt_thread_startup(tid);}else{rt_kprintf("Failed to create thread.\n");return -1;}return 0;
    }
    

    在上述示例中,我们使用 rt_thread_create_dyn() 函数动态创建线程,并指定线程名称、线程优先级、线程堆栈的大小、线程堆栈的数量、线程入口函数等参数。

无论是静态创建线程还是动态创建线程,都需要在创建后通过 rt_thread_startup() 函数启动线程。通过启动线程后,RT-Thread 内核会根据线程的优先级进行调度,实现多任务并发执行。

注意:在 RT-Thread 中,每个线程的入口函数都应该是一个无返回值且不带参数的函数。如果线程完成任务或者出现错误,应当通过 rt_thread_delete() 函数或线程函数的返回值来终止线程的执行。

rt_thread_create() 是用于创建线程的函数。它的函数原型如下:

rt_thread_t rt_thread_create(const char *name, void (*entry)(void *parameter), void *parameter,rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick)

参数的解释如下:

  • name:线程名称,用于标识唯一的线程。
  • entry:线程函数的入口地址,当线程被启动时会执行该函数。
  • parameter:线程函数的参数,用于传递给线程函数。
  • stack_size:线程使用的栈大小,以字节为单位。
  • priority:线程的优先级,范围从 0(最低优先级)到 RT_THREAD_PRIORITY_MAX(最高优先级)。
  • tick:线程的时间片长度,单位为 RT_TICK_PER_SECOND 的倍数。

示例用法请参考上面的回答。

rt_thread_startup() 是 RT-Thread 操作系统中用于启动线程的函数。

函数原型如下:

void rt_thread_startup(rt_thread_t thread);

参数 thread 是一个指向要启动的线程控制块的指针。

使用 rt_thread_startup() 函数可以将一个已经创建的线程标记为可运行状态,并开始调度执行线程的任务逻辑。

rt_thread_yield() 是用于让出 CPU 资源的函数。调用此函数会主动让出当前线程所占用的 CPU 时间片,使其他具有相同或更高优先级的线程得以运行。在多任务环境下,当一个线程不再需要继续运行或执行一段时间后想主动让出 CPU 时,可以调用 rt_thread_yield() 函数。

使用 rt_thread_yield() 的示例代码如下:

#include <rtthread.h>void thread1_entry(void *parameter)
{while (1) {/* 线程1 的任务逻辑 */// ...// 让出 CPU 执行权限rt_thread_yield();}
}void thread2_entry(void *parameter)
{while (1) {/* 线程2 的任务逻辑 */// ...// 让出 CPU 执行权限rt_thread_yield();}
}int main(void)
{rt_thread_t thread1, thread2;/* 创建线程1 */thread1 = rt_thread_create("thread1", thread1_entry, NULL, 512, 10, 20);if (thread1 != RT_NULL) {rt_thread_startup(thread1);}/* 创建线程2 */thread2 = rt_thread_create("thread2", thread2_entry, NULL, 512, 11, 20);if (thread2 != RT_NULL) {rt_thread_startup(thread2);}/* 主线程的任务逻辑 */while (1) {// ...}return 0;
}

在上述示例中,线程1 和线程2 分别执行各自的任务逻辑,并在需要时调用 rt_thread_yield() 函数主动释放 CPU 执行权限,以让其他具有相同或更高优先级的线程得以运行。主线程则执行其自身的任务逻辑。

请注意,尽管 rt_thread_yield() 可以主动让出 CPU 资源,但滥用它可能导致系统性能下降。因此,建议使用合理的逻辑和调度策略来决定何时调用 rt_thread_yield()

二、信号量(Semaphore)

2.1 概述

RT-Thread 是一个开源的嵌入式实时操作系统(RTOS),它提供了丰富的 API 接口来支持多任务、线程通信和同步机制等。在 RT-Thread 中,信号量(Semaphore)是一种常用的同步机制,用于控制任务对共享资源的访问。

RT-Thread 的信号量是通过rt_sem结构体来表示的,其定义如下:

struct rt_semaphore
{char                parent_flag;        /**< To RTC semaphore,this is 1;To Thread semaphore.this is 0 */unsigned short      value;              /**< The count value of the semaphore */struct rt_list_node  list;               /**< The waiting list of suspend threads.  */
};

RT-Thread 信号量的操作主要有两个:

  • rt_sem_init():用于初始化信号量,可以指定初始值。
  • rt_sem_take():用于获取信号量资源。如果信号量值大于 0,则减 1 并继续执行;如果信号量值等于 0,则当前任务会被阻塞,直到其他任务释放信号量资源。
  • rt_sem_release():用于释放信号量资源,并唤醒等待的任务。

下面是一个示例代码,演示如何在 RT-Thread 中使用信号量:

#include <rtthread.h>#define THREAD_PRIORITY    25
#define THREAD_STACK_SIZE  512
#define SEM_INIT_VALUE     5static rt_sem_t sem;static void thread_entry(void* parameter)
{rt_uint32_t count = 0;while (1){rt_sem_take(sem, RT_WAITING_FOREVER);   // 获取信号量资源rt_kprintf("Thread takes semaphore, count: %d\n", count++);rt_thread_mdelay(1000);rt_sem_release(sem);    // 释放信号量资源}
}int app_mysample_main(void)
{sem = rt_sem_create("my_sem", SEM_INIT_VALUE, RT_IPC_FLAG_FIFO);if (sem == RT_NULL){rt_kprintf("Failed to create semaphore.\n");return -1;}rt_thread_t tid = rt_thread_create("my_thread", thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, 10);if (tid != RT_NULL){rt_thread_startup(tid);}else{rt_kprintf("Failed to create thread.\n");return -1;}return 0;
}

在上述示例中,我们首先使用 rt_sem_create() 函数创建了一个信号量对象,指定初始值为 5。然后,创建了一个线程,并在线程的入口函数 thread_entry() 中使用 rt_sem_take() 函数获取信号量资源,每次获取成功后打印一条信息。然后,通过 rt_sem_release() 函数释放信号量资源。在 app_mysample_main() 函数中,启动了线程,并将其优先级设置为 25。

通过使用 RT-Thread 提供的信号量机制,可以实现任务之间的同步和资源控制,确保共享资源的安全访问。同时,也可以避免资源竞争和数据不一致等问题。

2.2 rt_sem_create 和 rt_sem_init的区别

在 RT-Thread 中,rt_sem_create 和 rt_sem_init 都用于创建和初始化信号量,但它们之间有一些区别。

  1. 创建对象的方式:rt_sem_create 创建一个新的信号量对象返回一个信号量指针,而 rt_sem_init 则是在已经定义的信号量对象上进行初始化

  2. 参数传递:rt_sem_create 函数需要传递 name 参数来为信号量指定一个名称,该名称用于区分不同的信号量。而 rt_sem_init 函数不需要传递名称参数,因为它是在已定义的信号量对象上进行初始化

  3. 对象的分配和定义:rt_sem_create 在内部创建一个新的信号量对象,并返回指向该对象的指针。而 rt_sem_init 需要在函数调用之前先定义并分配了信号量对象,然后在初始化时使用该对象。

  4. 代码结构:rt_sem_create 通常用于动态创建信号量对象,适用于需要在运行时根据条件动态创建信号量的情况。而 rt_sem_init 适用于已经定义好的信号量对象,在编译时已经指定了信号量对象的数量和大小。

举个例子,假设我们需要在运行时根据某个条件动态创建信号量,那么可以使用 rt_sem_create 函数。示例如下:

#include <rtthread.h>void create_dynamic_semaphore(void)
{rt_sem_t dyn_sem = rt_sem_create("dynamic_sem", 1, RT_IPC_FLAG_FIFO);/* 后续可以使用 dyn_sem 进行信号量的操作,如等待信号量和释放信号量 *//* 使用完毕后需要删除信号量对象 */rt_sem_delete(dyn_sem);
}

另一方面,如果我们在编译时已经知道需要使用多少个信号量对象,并且将它们定义在全局作用域中,那么可以使用 rt_sem_init 函数对已定义的信号量对象进行初始化。示例如下:

#include <rtthread.h>static rt_sem_t my_sem; // 已定义的信号量对象int main(void)
{rt_sem_init(my_sem, "my_sem", 1, RT_IPC_FLAG_FIFO);/* 后续可以使用 my_sem 进行信号量的操作,如等待信号量和释放信号量 */return 0;
}

需要根据具体的应用场景,选择适合的方式来创建和初始化信号量对象。

三、互斥锁(Mutex)

RT-Thread 中的互斥锁(Mutex)是一种用于保护共享资源的同步机制。它可以确保在任意时刻只有一个线程能够访问被保护的共享资源,以防止多个线程同时修改数据而导致的冲突和错误。

RT-Thread 提供了 rt_mutex_t 数据类型来表示互斥锁,并提供了一系列函数来操作互斥锁,包括创建互斥锁、申请锁、释放锁等。下面是互斥锁的基本使用方法:

  1. 创建互斥锁:可以使用 rt_mutex_create() 函数来创建互斥锁。

    #include <rtthread.h>static rt_mutex_t mutex;int app_mysample_main(void)
    {mutex = rt_mutex_create("my_mutex", RT_IPC_FLAG_FIFO);if (mutex == RT_NULL){rt_kprintf("Failed to create mutex.\n");return -1;}return 0;
    }
    

    在上述示例中,我们通过 rt_mutex_create() 函数创建了一个名为 “my_mutex” 的互斥锁,并指定了互斥锁的属性为 FIFO(先进先出)。

  2. 申请互斥锁:当需要访问共享资源时,可以使用 rt_mutex_take() 函数来申请互斥锁。如果当前互斥锁已经被其他线程持有,申请线程会被阻塞,直到获得互斥锁为止。

    rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout);
    

    mutex 参数是要申请的互斥锁对象;timeout 参数是申请锁的超时时间,如果设置为 0,则表示一直等待直到获得互斥锁,如果设置为 RT_WAITING_FOREVER,则表示永久等待;函数的返回值表示申请锁的结果,如果成功则返回 RT_EOK

    #include <rtthread.h>void thread_entry(void* parameter)
    {if (rt_mutex_take(mutex, RT_WAITING_FOREVER) == RT_EOK){/* 访问共享资源 *//* 在这里进行操作 */rt_mutex_release(mutex);}else{rt_kprintf("Failed to take mutex.\n");}
    }
    

    在上述示例中,我们通过 rt_mutex_take() 函数申请互斥锁 mutex,并设置超时时间为永久等待 RT_WAITING_FOREVER。如果申请成功,则可以在获得锁后访问共享资源;如果申请失败,则表示等待锁超时,需要处理相应的错误逻辑。

  3. 释放互斥锁:当完成对共享资源的访问后,需要使用 rt_mutex_release() 函数释放互斥锁。

    rt_err_t rt_mutex_release(rt_mutex_t mutex);
    

    在释放互斥锁后,其他等待该互斥锁的线程有机会获得该锁,进而访问共享资源。

    #include <rtthread.h>void thread_entry(void* parameter)
    {if (rt_mutex_take(mutex, RT_WAITING_FOREVER) == RT_EOK){/* 访问共享资源 *//* 在这里进行操作 */rt_mutex_release(mutex);}else{rt_kprintf("Failed to take mutex.\n");}
    }
    

    在上述示例中,我们通过 rt_mutex_release() 函数释放之前申请的互斥锁 mutex

使用互斥锁时需要注意以下几点:

  • 互斥锁只能由申请锁的线程来释放,否则会导致未定义行为和死锁问题。
  • 在申请锁和释放锁之间的代码不应该阻塞,以避免死锁情况。
  • 尽量避免多个线程嵌套申请同一个互斥锁,以防止死锁问题的发生。

通过合理的使用互斥锁,可以保护共享资源,解决多线程访问的竞争和冲突问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/688057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dm_control 翻译: Software and Tasks for Continuous Control

dm_control: Software and Tasks for Continuous Control dm_control&#xff1a;连续控制软件及任务集 文章目录 dm_control: Software and Tasks for Continuous Controldm_control&#xff1a;连续控制软件及任务集Abstract1 Introduction1 引言1.1 Software for research1…

Java - SPI机制

本文参考&#xff1a;SPI机制 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是JDK内置的一种服务提供发现机制&#xff0c;可以用来启动框架扩展和替换组件&#xff0c;主要是被框架的开发人员使用&#xff0c;比如 java.sql.Driver接口&#xff0c;其他…

TensorRT转换onnx的Transpose算子遇到的奇怪问题

近来把一个模型导出为onnx并用onnx simplifier化简后转换为TensorRT engine遇到非常奇怪的问题&#xff0c;在我们的网络中有多个检测头时&#xff0c;转换出来的engine的推理效果是正常的&#xff0c;当网络中只有一个检测头时&#xff0c;转换出来的engine的推理效果奇差&…

动态代理IP如何选择?

IP地址是由IP协议所提供的一种统一的地址格式&#xff0c;通过为每一个网络和每一台主机分配逻辑地址的方式来屏蔽物理地址的差异。根据IP地址的分配方式&#xff0c;IP可以分为动态IP与静态IP两种。对于大部分用户而言&#xff0c;日常使用的IP地址均为动态IP地址。从代理IP的…

LeetCode 0429.N 叉树的层序遍历:广度优先搜索(BFS)

【LetMeFly】429.N 叉树的层序遍历&#xff1a;广度优先搜索(BFS) 力扣题目链接&#xff1a;https://leetcode.cn/problems/n-ary-tree-level-order-traversal/ 给定一个 N 叉树&#xff0c;返回其节点值的层序遍历。&#xff08;即从左到右&#xff0c;逐层遍历&#xff09;…

【软件工具】编译原理简介与工程实践

引论 静态VS动态 有3个角度&#xff1a;策略、作用域、内存位置。静态策略即编译时刻判定&#xff0c;动态策略即运行时刻。 名字、位置、值&#xff0c;两者之间通过环境、状态两种映射来表示。 #mermaid-svg-YRdKbkmkpRXeT7Tz {font-family:"trebuchet ms",verdana…

aiofiles:解锁异步文件操作的神器

aiofiles&#xff1a;解锁异步文件操作的神器 在Python的异步编程领域&#xff0c;文件操作一直是一个具有挑战性的任务。传统的文件操作函数在异步环境下无法发挥其最大的潜力&#xff0c;而aiofiles库应运而生。aiofiles是一个针对异步I/O操作的Python库&#xff0c;它简化了…

C#使用迭代器实现文字的动态效果

目录 一、涉及到的知识点 1.GDI 2.Thread类 3.使用IEnumerable()迭代器 二、实例 1.源码 2.生成效果&#xff1a; 一、涉及到的知识点 1.GDI GDI主要用于在窗体上绘制各种图形图像。 GDI的核心是Graphics类&#xff0c;该类表示GDI绘图表面&#xff0c;它提供将对象绘制…

不等式的证明之二

不等式的证明之二 证明下述不等式证法一证法二证法二的补充 证明下述不等式 设 a , b , c a,b,c a,b,c 是正实数&#xff0c;请证明下述不等式&#xff1a; 11 a 5 a 6 b 11 b 5 b 6 c 11 c 5 c 6 a ≤ 3 \begin{align} \sqrt{\frac{11a}{5a6b}}\sqrt{\frac{11b}{5b6c}…

预防.locked.locked1勒索病毒攻击:保护数据安全

导言&#xff1a; 随着科技的发展&#xff0c;网络安全问题日益严重&#xff0c;其中勒索病毒是一种令人头痛的威胁。.locked和.locked1是两种常见的勒索病毒&#xff0c;它们会将用户的数据文件加密&#xff0c;并要求支付赎金以获取解密密钥。本文将介绍这两种勒索病毒的特点…

leetcode hot100不同路径

本题可以采用动态规划来解决。还是按照五部曲来做 确定dp数组&#xff1a;dp[i][j]表示走到&#xff08;i&#xff0c;j&#xff09;有多少种路径 确定递推公式&#xff1a;我们这里&#xff0c;只有两个移动方向&#xff0c;比如说我移动到&#xff08;i&#xff0c;j&#x…

【实战】二、Jest难点进阶(二) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(六)

文章目录 一、Jest 前端自动化测试框架基础入门二、Jest难点进阶2.mock 深入学习 学习内容来源&#xff1a;Jest入门到TDD/BDD双实战_前端要学的测试课 相对原教程&#xff0c;我在学习开始时&#xff08;2023.08&#xff09;采用的是当前最新版本&#xff1a; 项版本babel/co…

STM32 寄存器操作 systick 滴答定时器 与中断

一、什么是 SysTick SysTick—系统定时器是属于CM3内核中的一个外设&#xff0c;内嵌在NVIC中。系统定时器是一个24bit的向下递减的计数器&#xff0c; 计数器每计数一次的时间为1/SYSCLK&#xff0c;一般我们设置系统时钟SYSCLK等于72M。当重装载数值寄存器的值递减到0的时候…

ES6中的数组解构赋值【详解】

文章目录 1.数组的解构赋值1.1 基本用法1.2 默认值1.3更多对象解构赋值 1.数组的解构赋值 1.1 基本用法 ES6允许按照一定的模式&#xff0c;从数组和对象中提取值&#xff0c;对变量进行赋值&#xff0c;这被称为结构。 let [a, b, c] [1, 2, 3];如果解构不成功&#xff0c…

python学习(三):pip安装及如何加速安装第三方组件

pip全称Package Installer for Python&#xff0c;即用来安装第三方组件的工具 一.安装pip Python3中setuptools、Pip安装详解 1、安装setuptools 命令如下&#xff1a; wget --no-check-certificate https://pypi.python.org/packages/source/s/setuptools/setuptools-19.…

不确定性、先验概率_后验概率、概率密度、贝叶斯法则、朴素贝叶斯_、最大似然估计

【人工智能】— 不确定性、先验概率/后验概率、概率密度、贝叶斯法则、朴素贝叶斯 文章目录 【人工智能】— 不确定性、先验概率/后验概率、概率密度、贝叶斯法则、朴素贝叶斯不确定性不确定性与理性决策基本概率符号先验概率(无条件概率)/后验概率(条件概率)随机变量概率密度联…

【web | CTF】BUUCTF [网鼎杯 2020 青龙组]AreUSerialz

天命&#xff1a;php的序列化题目简直是玄学&#xff0c;既不能本地复现&#xff0c;也不能求证靶场环境 天命&#xff1a;本地php是复现不了反序列化漏洞的&#xff0c;都不知道是版本问题还是其他问题 天命&#xff1a;这题也是有点奇怪的&#xff0c;明明用字符串2也应该是可…

优雅地用eruda在移动端上调试网页

eruda简介 github开源项目网址:eruda Eruda 是一个专为手机网页前端设计的调试面板,类似 DevTools 的迷你版,其主要功能包括:捕获 console 日志、检查元素状态、捕获XHR请求、显示本地存储和 Cookie 信息等等。 其有以下功能: Console面板:捕获Console日志,支持log、…

C语言—指针

碎碎念:做指针题的时候我仿佛回到了原点&#xff0c;总觉得目的是为了把框架搭建起来&#xff0c;我胡说的哈31 1.利用指针变量将一个数组中的数据反向输出。 /*1.利用指针变量将一个数组中的数据反向输出。*/#include <stdio.h> #include <time.h> #include <…

phpstrom创建thinkphp项目

安装php和composer 参考 安装phpstrom 创建项目 查看thinkphp版本 https://packagist.org/packages/topthink/think 打开所在项目编辑配置 即可调试运行