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

本文详细介绍了RT-Thread中线程的管理,包括线程的概念、创建方式以及线程间的通信同步机制,重点讲解了信号量和互斥锁的原理、操作方法及其在资源保护中的应用。
摘要由CSDN通过智能技术生成

目录

一、线程管理(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     5

static 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

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

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

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

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文火冰糖的硅基工坊

你的鼓励是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值