SoC如何实现线程安全?

原创 美男子玩编程 2024-12-15 08:00

点击上方蓝色字体,关注我们

在 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_lock 和 pthread_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_create、pthread_setspecific 和 pthread_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 key return 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 提供了更灵活的数据管理方式,使得每个线程能够独立存储和访问它的特有数据,避免数据竞争问题。

点击阅读原文,更精彩~

美男子玩编程 多领域、有深度的开发者交流平台
评论
  • 书接上回:【2022年终总结】阳光总在风雨后,启航2023-面包板社区  https://mbb.eet-china.com/blog/468701-438244.html 总结2019,松山湖有个欧洲小镇-面包板社区  https://mbb.eet-china.com/blog/468701-413397.html        2025年该是总结下2024年的喜怒哀乐,有个好的开始,才能更好的面对2025年即将
    liweicheng 2025-01-24 23:18 329浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 470浏览
  • 不让汽车专美于前,近年来哈雷(Harley-Davidson)和本田(Honda)等大型重型机车大厂的旗下车款皆已陆续配备车载娱乐系统与语音助理,在路上也有越来越多的普通机车车主开始使用安全帽麦克风,在骑车时透过蓝牙连线执行语音搜寻地点导航、音乐播放控制或免持拨打接听电话等各种「机车语音助理」功能。客户背景与面临的挑战以本次分享的客户个案为例,该客户是一个跨国车用语音软件供货商,过往是与车厂合作开发前装车机为主,且有着多年的「汽车语音助理」产品经验。由于客户这次是首度跨足「机车语音助理」产品,因
    百佳泰测试实验室 2025-01-24 17:00 191浏览
  • 前篇文章中『服务器散热效能不佳有解吗?』提到气冷式的服务器其散热效能对于系统稳定度是非常重要的关键因素,同时也说明了百佳泰对于散热效能能提供的协助与服务。本篇将为您延伸说明我们如何进行评估,同时也会举例在测试过程中发现的问题及改善后的数据。AI服务器的散热架构三大重点:GPU导风罩:尝试不同的GPU导风罩架构,用以集中服务器进风量,加强对GPU的降温效果。GPU托盘:改动GPU托盘架构,验证出风面积大小对GPU散热的影想程度。CPU导风罩:尝试封闭CPU导风罩间隙,集中风流,验证CPU降温效果。
    百佳泰测试实验室 2025-01-24 16:58 183浏览
  • 项目展示①正面、反面②左侧、右侧项目源码:https://mbb.eet-china.com/download/316656.html前言为什么想到要做这个小玩意呢,作为一个死宅,懒得看手机,但又想要抬头就能看见时间和天气信息,于是就做个这么个小东西,放在示波器上面正好(示波器外壳有个小槽,刚好可以卡住)功能主要有,获取国家气象局的天气信息,还有实时的温湿度,主控采用ESP32,所以后续还可以开放更多奇奇怪怪的功能,比如油价信息、股票信息之类的,反正能联网可操作性就大多了原理图、PCB、面板设计
    小恶魔owo 2025-01-25 22:09 561浏览
  • 飞凌嵌入式基于瑞芯微RK3562系列处理器打造的FET3562J-C全国产核心板,是一款专为工业自动化及消费类电子设备设计的产品,凭借其强大的功能和灵活性,自上市以来得到了各行业客户的广泛关注。本文将详细介绍如何启动并测试RK3562J处理器的MCU,通过实际操作步骤,帮助各位工程师朋友更好地了解这款芯片。1、RK3562J处理器概述RK3562J处理器采用了4*Cortex-A53@1.8GHz+Cortex-M0@200MHz架构。其中,4个Cortex-A53核心作为主要核心,负责处理复杂
    飞凌嵌入式 2025-01-24 11:21 280浏览
  • 故障现象 一辆2007款日产天籁车,搭载VQ23发动机(气缸编号如图1所示,点火顺序为1-2-3-4-5-6),累计行驶里程约为21万km。车主反映,该车起步加速时偶尔抖动,且行驶中加速无力。 图1 VQ23发动机的气缸编号 故障诊断接车后试车,发动机怠速运转平稳,但只要换挡起步,稍微踩下一点加速踏板,就能感觉到车身明显抖动。用故障检测仪检测,发动机控制模块(ECM)无故障代码存储,且无失火数据流。用虹科Pico汽车示波器测量气缸1点火信号(COP点火信号)和曲轴位置传感器信
    虹科Pico汽车示波器 2025-01-23 10:46 311浏览
  • 随着AI大模型训练和推理对计算能力的需求呈指数级增长,AI数据中心的网络带宽需求大幅提升,推动了高速光模块的发展。光模块作为数据中心和高性能计算系统中的关键器件,主要用于提供高速和大容量的数据传输服务。 光模块提升带宽的方法有两种:1)提高每个通道的比特速率,如直接提升波特率,或者保持波特率不变,使用复杂的调制解调方式(如PAM4);2)增加通道数,如提升并行光纤数量,或采用波分复用(CWDM、LWDM)。按照传输模式,光模块可分为并行和波分两种类型,其中并行方案主要应用在中短距传输场景中成本
    hycsystembella 2025-01-25 17:24 429浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 234浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 449浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦