【C语言安全编码之可重入函数】2、线程安全

简介: 【C语言安全编码之可重入函数】2、线程安全

线程定义


  • Linux中进程的最小执行单位就是线程,一个进程可以包含一个或多个线程,但至少会有一个线程,如下图所示:



线程的常见模型


三种模型


  • 多进程模式:每个进程只有一个线程;



  • 多线程模式:一个进程有多个线程;



  • 多进程+多线程模式:复杂度最高;



线程安全中的线程模型


  • 多线程实际的模型如下:



  • 该模型下,多个线程间共享代码、数据和文件,本文探讨的线程安全,也是基于该模型。


线程安全的定义


维基百科


  • 线程安全是适用于多线程代码的计算机编程概念,其仅以确保所有线程正常运行并满足其设计规范而没有意外交互的方式操作共享数据结构。


  • 一个程序可以在共享地址空间中同时在多个线程中执行代码,其中每个线程都可以访问几乎所有其他线程的内存。 线程安全是一种属性,它允许代码通过同步的方式重新建立实际控制流和程序文本之间的一些对应关系,从而在多线程环境中运行。


《Java并发编程实践》


当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。


《The Linux Programming Interface》


  • 如果一个函数可以被多个线程安全地调用,则称该函数是线程安全的; 反之,如果一个函数不是线程安全的,那么当它在另一个线程中执行时,我们就不能从一个线程调用它。


线程安全的常见场景


  • 多处理器(MP)下的多线程最为典型。在同一个进程环境下,运行在不同处理器上的不同线程可能会同时访问临界资源,从而引出诸如临界区、互斥/竞争访问的概念。


  • 即便在单处理器(UP)下上述问题也是存在的,如果没有对临界资源的synchronization,可能处理器在运行一个线程的临界区代码时(由于外部中断触发的调度等等)被切换到相同进程的另一个线程,也进入了相同资源的临界区,从而危害到线程安全。


线程安全的不同层级


  • 库函数级别:可以提供一定的线程安全保证;


  • 架构层面:防止或限制不同形式死锁风险的设计,以及最大化并发性能的优化等;


达成线程安全的方法


避免共享状态的情况


  • 可重入:


将状态信息保存在每次执行的本地变量中,通常在堆栈上,而不是静态或全局变量或其他非本地状态。所有非本地状态都必须通过原子操作访问,并且数据结构也必须是可重入的。


  • 线程本地存储


变量是本地化的,因此每个线程都有自己的私有副本。


  • 不可变对象


对象的状态在构造后无法更改。这意味着只共享只读数据并获得固有的线程安全性。然后可以以创建新对象而不是修改现有对象的方式实现可变(非常量)操作。


同步相关,用于无法避免共享状态的情况:


  • 互斥


  • 对共享数据的访问使用确保在任何时候只有一个线程读取或写入共享数据的机制进行序列化。


  • 互斥的结合需要经过深思熟虑,因为不正确的使用会导致死锁、活锁和资源匮乏等副作用。


  • 原子操作


  • 通过使用不能被其他线程中断的原子操作来访问共享数据。 这通常需要使用特殊的机器语言指令,这些指令可能在运行时库中可用。


  • 由于操作是原子的,因此共享数据始终保持有效状态,无论其他线程如何访问它。 原子操作构成了许多线程锁定机制的基础,并用于实现互斥原语。


线程安全的例子


  • 例1、increment_counter函数是线程安全的


# include <pthread.h>
int increment_counter ()
{
  static int counter = 0;
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  // only allow one thread to increment at a time
  pthread_mutex_lock(&mutex);
  ++counter;
  // store value before any other threads increment it further
  int result = counter;
  pthread_mutex_unlock(&mutex);
  return result;
}


例2、Counter类的inc方法是线程安全的


class Counter {
    private int i = 0;
    public synchronized void inc() {
        i++;
    }
}
相关文章
|
26天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
17天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
31 10
|
10天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
16天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
41 7
|
16天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
25 4
|
21天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
29天前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
43 6
|
27天前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
31 2
|
29天前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
19 3
|
29天前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
21 0
C++ 多线程之线程管理函数