SoC如何实现线程安全?六大机制与代码详解

2026-10-19 10:05:42 7阅读

在 SoC 上实现线程安全,是多线程编程中非常关键的问题,特别是当涉及资源共享时。线程安全的设计目的是避免多个线程同时访问共享资源时出现竞争条件、数据竞争等问题。

1、线程栈(Thread Stack)

在多线程系统中,每个线程都有自己独立的栈,用于存储局部变量、函数调用返回地址等。栈的独立性是线程安全的基础之一——栈是线程私有的,不与其他线程共享。因此,所有存储在栈上的数据(比如局部变量、函数参数)默认是线程安全的。

SoC 上的多核处理器或多线程操作系统(如 Linux、RTOS)为每个线程分配独立的栈。在 Cortex-M 系列的 ARM 处理器上,栈指针(SP)用于跟踪每个线程的栈顶位置。中断期间发生线程切换时,处理器需要保存当前线程的栈指针并加载下一个线程的栈指针,这一过程称为"上下文切换",它依赖栈的独立性来确保线程安全。

示例中 local_var 是局部变量,位于栈中,因此不同线程各自拥有自己的 local_var,不会造成资源竞争:

void thread_function() {
    int local_var = 0;  // 局部变量保存在当前线程的栈中
    local_var += 1;
    printf("Thread local variable: %d
", local_var);
}

2、可重入函数(Reentrant Function)

可重入函数是指多个线程可以同时调用而不会引发竞争条件的函数。要实现可重入,函数必须:不使用静态数据(除非有线程安全的保护机制)、不依赖共享资源(或对共享资源进行保护,如加锁)、不返回指向静态或全局数据的指针。

静态变量在全局内存中分配、被所有线程共享,多个线程同时访问或修改时会导致数据竞争;需通过锁或其他同步机制保护。递归函数若使用全局或静态数据,在不同线程递归调用时也会出问题。

示例中 non_reentrant_function 里的 static_var 是共享的,多个线程访问会产生竞态条件;而 reentrant_function 仅使用局部变量,是线程安全的:

int non_reentrant_function(int a) {
    static int static_var = 0;  // 静态变量导致线程不安全
    static_var += a;
    return static_var;
}

int reentrant_function(int a, int b) {
    return a + b;  // 不使用任何共享资源,线程安全
}

3、线程安全函数(Thread-Safe Function)

为了让函数在并发环境下线程安全,常用同步机制保护共享资源,典型包括:

  • 互斥锁(Mutex):防止多个线程同时访问共享资源。
  • 读写锁(Read-Write Lock):允许多个线程同时读,写操作排他。
  • 信号量(Semaphore):控制访问共享资源的线程数量。
  • 自旋锁(Spinlock):当需要保护的代码执行时间很短时,可避免线程休眠、提高效率。

在资源竞争较少时,互斥锁可能导致性能下降(SoC 系统上线程切换和上下文切换开销较大),所以嵌入式系统中常用自旋锁代替。自旋锁适用于多核处理器上线程能在短时间内完成任务的场景,但它会让线程忙等待、消耗 CPU 资源。

示例通过 pthread_mutex_lock 和 pthread_mutex_unlock 对共享变量加解锁,确保同一时刻只有一个线程能修改 shared_resource:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_resource = 0;

void* thread_safe_function(void* arg) {
    pthread_mutex_lock(&mutex);  // 加锁保护共享资源
    shared_resource += 1;
    printf("Shared resource: %d
", shared_resource);
    pthread_mutex_unlock(&mutex);  // 解锁
    return NULL;
}

4、一次性初始化(One-Time Initialization)

多线程环境中,某些共享资源或全局变量只需要初始化一次(如全局配置或共享对象)。可用 pthread_once 保证只初始化一次。

为了减少加锁开销,有时会使用双重检查锁定:先检查是否已初始化,未初始化再加锁并初始化。在某些多核处理器上,编译器和 CPU 可能重排指令,导致初始化操作和赋值顺序不一致,此时内存屏障可确保多核系统中操作顺序一致。

示例中 pthread_once 确保无论多少线程,initialize_resource 都只会被调用一次:

pthread_once_t once_control = PTHREAD_ONCE_INIT;

void initialize_resource() {
    printf("Resource initialized.
");
}

void* thread_function(void* arg) {
    pthread_once(&once_control, initialize_resource);  // 保证初始化只发生一次
    printf("Thread is running.
");
    return NULL;
}

5、线程特有数据(Thread-Specific Data, TSD)

TSD 允许每个线程拥有自己的数据副本,不被其他线程访问。可用 pthread_key_create、pthread_setspecific 和 pthread_getspecific 管理。每个线程都有独立的存储空间保存自己的特有数据,因此不会引发竞态条件。

实现上 TSD 通常用哈希表或数组存储,每个线程都有独立副本,操作系统或线程库负责在上下文切换时保存和恢复。线程退出时应确保特有数据被适当销毁,可通过设置销毁函数(destructor)自动释放资源。

pthread_key_t tls_key;

void destructor(void* value) {
    free(value);  // 销毁线程特定的数据
}

void* thread_function(void* arg) {
    int* thread_data = (int*)malloc(sizeof(int));
    *thread_data = 1;
    pthread_setspecific(tls_key, thread_data);  // 设置当前线程的特有数据

    printf("Thread-specific data: %d
", *(int*)pthread_getspecific(tls_key));
    return NULL;
}

int main() {
    pthread_key_create(&tls_key, destructor);  // 创建 TLS key 并设置销毁函数

    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, thread_function, NULL);
    pthread_create(&thread2, NULL, thread_function, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_key_delete(tls_key);  // 删除 TLS key
    return 0;
}

6、线程局部存储(Thread Local Storage, TLS)

线程局部存储是更方便的方式,允许每个线程拥有一组私有的全局变量副本,避免线程间共享。多数编译器提供内置支持,例如 GCC 中用 __thread 关键字声明线程局部变量。

在 SoC 或嵌入式操作系统上,TLS 通常通过 CPU 上下文切换机制实现:每个线程的上下文不仅包括寄存器,还包括 TLS 数据段。TLS 数据结构通常存储在线程控制块(TCB)中,每个线程拥有独立的 TCB。

示例中 __thread 声明的 tls_var 是线程局部变量,每个线程都有自己的副本,因此线程安全:

__thread int tls_var = 0;

void* thread_function(void* arg) {
    tls_var += 1;
    printf("TLS variable: %d
", tls_var);
    return NULL;
}

SoC 上的线程安全设计需要在多个层面加以考虑,从栈隔离到复杂的同步机制(互斥锁、信号量等),不同机制有不同应用场景。线程特有数据和 TLS 提供了更灵活的数据管理方式,让每个线程能独立存储和访问自己的数据,避免数据竞争。


作者:不脱发的程序猿
链接:https://www.aiddithome.com/p/1dfa8bb4900785.html
本作遵循 [CC BY 4.0 署名] 许可,版权归作者所有。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
3条评论
电烙铁爱好者
1楼 · 2天前

线程安全这块把栈隔离、锁、TLS 几个层面讲清楚了,梳理得很有条理。

拆机党
2楼 · 2天前

SoC上线程切换开销大,文中提到用自旋锁替代互斥锁的场景很实用。

锡点
3楼 · 2天前

嵌入式里 pthread_once 和线程特有数据平时用得少,讲明白了很有价值。