当前位置: 首页 > news >正文

SoC如何实现线程安全?

目录

1、线程栈(Thread Stack)

2、可重入函数(Reentrant Function)

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

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

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

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


在 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\n", local_var);
}

2、可重入函数(Reentrant Function)

可重入函数是指多个线程可以同时调用而不会引发竞争条件的函数。为了实现可重入,函数必须:

  1. 不使用静态数据(除非有线程安全的保护机制)。
  2. 不依赖共享资源,或者对共享资源进行保护(如通过锁定机制)。
  3. 不返回指向静态或全局数据的指针。

细节分析:

  • 静态变量的危险性:静态变量在全局内存中分配,所有线程共享该变量,因此多个线程同时访问或修改静态变量时会导致数据竞争。如果需要使用静态数据,必须通过锁或其他同步机制来保护它。
  • 递归函数问题:递归函数需要特别注意,如果递归函数使用全局或静态数据,那么它在不同线程递归调用时就会出现问题。

以下示例中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_lockpthread_mutex_unlock 对共享变量 shared_resource 进行加锁和解锁操作,确保只有一个线程可以在同一时间修改 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\n", shared_resource);pthread_mutex_unlock(&mutex);  // 解锁return NULL;
}

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

在多线程环境中,有时某个共享资源或全局变量只需要初始化一次,比如一个全局配置或共享对象。为了确保初始化操作的线程安全,可以使用 pthread_once 来保证只初始化一次。

细节分析:

  • 双重检查锁定(Double-Checked Locking):为了减少加锁开销,有时会使用双重检查锁定,先检查是否已经初始化,如果没有再加锁并初始化。双重检查锁定是为了避免不必要的锁定开销,特别是在初始化后频繁读取全局变量的场景中。

  • 内存屏障问题:在某些多核处理器上,编译器和 CPU 可能会重排指令,导致初始化操作和赋值顺序不一致。为了解决这个问题,内存屏障可以确保在多核系统中操作的顺序一致。

以下示例pthread_once 确保无论有多少个线程,initialize_resource 函数都只会被调用一次,保证了线程安全的初始化。

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

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

线程特有数据(Thread-Specific Data, TSD)允许每个线程拥有自己的数据副本,而这些数据不会被其他线程访问。可以使用 pthread_key_createpthread_setspecificpthread_getspecific 来管理线程特有数据。每个线程都有独立的存储空间来保存它的特有数据,因此不会引发竞态条件。

细节分析:

  • 线程局部存储的工作原理:在实现上,TSD 通常使用哈希表或数组进行存储,每个线程都有自己的独立副本,操作系统或线程库负责在上下文切换时保存和恢复线程的特有数据。

  • 销毁线程特有数据:当线程退出时,应该确保特有数据得到适当销毁。这通常可以通过设置销毁函数(destructor)来自动释放资源。

以下示例每个线程都通过 pthread_setspecific 设置自己的特有数据,并通过 pthread_getspecific 获取数据,保证了每个线程的数据是独立的,线程安全。

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\n", *(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 keyreturn 0;
}

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

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

细节分析:

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

以下示例__thread 关键字声明的 tls_var 是线程局部变量,每个线程都有自己的副本,因此是线程安全的。

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

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

http://www.dtcms.com/a/353844.html

相关文章:

  • 【进阶篇第五弹】《详解存储过程》从0掌握MySQL中的存储过程以及存储函数
  • TypeScript:Interface接口
  • 如何启动一个分支网络改造试点?三步走
  • 【链表 - LeetCode】25. K 个一组翻转链表
  • 干眼症护理学注意事项
  • linux下的网络编程(2)
  • 技术分析 | Parasoft C/C++test如何突破单元测试的隔离难题
  • 亚马逊关键词策略全解析:类型、工具与多账号运营优化指南
  • AT_abc406_f [ABC406F] Compare Tree Weights
  • Windows/Linux 环境下 Jmeter 性能测试的安装与使用
  • 基于SpringBoot的宠物领养服务系统【2026最新】
  • MySQL 面试题系列(五)
  • Unity自定义Inspector面板之使用多选框模拟单选框
  • 前端技术演进录:从 AI 革命到架构新生
  • 【Linux】常用命令 拥有者和权限(四)
  • Python随机选择完全指南:从基础到高级工程实践
  • 安全向量模板类SiVector
  • vue 前端 区域自适应大小
  • AWS申请增加弹性IP配额流程
  • 《Vuejs设计与实现》第 17 章(编译优化)
  • 机器视觉学习-day05-图片颜色识别及颜色替换
  • # 快递单号查询系统:一个现代化的物流跟踪解决方案
  • YOLO12n-Deepsort多目标跟踪之昆虫数据集
  • 【C++标准库】<ios>详解基于流的 I/O
  • 科技赋能生态,智慧守护农林,汇岭生态开启农林产业现代化新篇章
  • C# OpenCVSharp 实现物体尺寸测量方案
  • Whisper JAX:突破性实时语音识别加速框架,性能提升70倍的开源解决方案
  • Spring : IOC / DI (控制反转 / 依赖注入)
  • C/C++---前缀和(Prefix Sum)
  • 【重学MySQL】九十一、MySQL远程登录