目录
一、线程管理(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 中创建线程的方法有两种:
-
静态创建线程:静态创建线程通过在源代码中直接定义线程对象的方式来创建。静态创建线程需要在编译时确定线程数量和优先级,并且需要预先分配好线程的栈空间。这种方式适合线程数量和优先级固定不变的场景。
静态创建线程的示例代码如下:
#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()
函数创建线程,并指定线程名称、线程入口函数、线程栈空间的大小、线程优先级等信息。 -
动态创建线程:动态创建线程通过在运行时动态申请内存来创建线程对象。动态创建线程的方式更加灵活,可以在运行时根据需要动态创建和销毁线程。
动态创建线程的示例代码如下:
#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
都用于创建和初始化信号量,但它们之间有一些区别。
-
创建对象的方式:
rt_sem_create
创建一个新的信号量对象,返回一个信号量指针,而rt_sem_init
则是在已经定义的信号量对象上进行初始化。 -
参数传递:
rt_sem_create
函数需要传递name
参数来为信号量指定一个名称,该名称用于区分不同的信号量。而rt_sem_init
函数不需要传递名称参数,因为它是在已定义的信号量对象上进行初始化。 -
对象的分配和定义:
rt_sem_create
在内部创建一个新的信号量对象,并返回指向该对象的指针。而rt_sem_init
需要在函数调用之前先定义并分配了信号量对象,然后在初始化时使用该对象。 -
代码结构:
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
数据类型来表示互斥锁,并提供了一系列函数来操作互斥锁,包括创建互斥锁、申请锁、释放锁等。下面是互斥锁的基本使用方法:
-
创建互斥锁:可以使用
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(先进先出)。 -
申请互斥锁:当需要访问共享资源时,可以使用
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
。如果申请成功,则可以在获得锁后访问共享资源;如果申请失败,则表示等待锁超时,需要处理相应的错误逻辑。 -
释放互斥锁:当完成对共享资源的访问后,需要使用
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
。
使用互斥锁时需要注意以下几点:
- 互斥锁只能由申请锁的线程来释放,否则会导致未定义行为和死锁问题。
- 在申请锁和释放锁之间的代码不应该阻塞,以避免死锁情况。
- 尽量避免多个线程嵌套申请同一个互斥锁,以防止死锁问题的发生。
通过合理的使用互斥锁,可以保护共享资源,解决多线程访问的竞争和冲突问题。