线程安全与可重入函数

一、线程安全

   在目前线程是操作系统调度的最小单元,进程是资源分配的最小单元。在大多数操作系统中,一个进程可以同时派生出多个线程。这些线程独立执行,共享进程的资源。线程主要由控制流程和资源使用两部分构成,因此一个不得不面对的问题就是对共享资源的访问。为了确保资源得到正确的使用,我们在设计编写程序时需要考虑避免竞争条件和死锁,需要更多地考虑使用线程互斥变量。

如果我们的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

线程安全 (Thread-safe) 的函数就是一个在代码层面解决上述问题比较好的方法,也成为多线程编程中的一个关键技术。如果在多线程并发执行的情况下,一个函数可以安全地被多个线程并发调用,可以说这个函数是线程安全的。反之,则称之为“非线程安全”函数。注意:在单线程环境下,没有“线程安全”和“非线程安全”的概念。因此,一个线程安全的函数允许任意地被任意的线程调用,在被调用时不需要考虑锁和资源访问控制,这在很大程度上会降低软件的死锁故障和资源并发访问冲突的机率。所以,我们应尽可能编写和调用线程安全函数。

我们可以通过下面这几条确定一个函数是线程不安全的。

a, 函数中访问全局变量和堆。

b, 函数中分配,重新分配释放全局资源。

c, 函数中通过句柄和指针的不直接访问。

d, 函数中使用了其他线程不安全的函数或者变量。

如下函数Sum()则是线程安全的:

例1:

int Sum(int x, int y) {       return (x+y);  }
但如果按下面的方法修改,Sum()就不再是线程安全的,因为它调用的函数 sum_counter()不是线程安全的,该函数访问了未加锁保护的全局变量 sum_value。这样的代码在单线程环境下不会有任何问题,但如果调用者是在多线程环境中,因为Sum()有可能被并发调用,所以全局变量 sum_value很有可能被并发修改,从而导致计数出错。例2:static int sum_value = 0; void sum_counter() {      sum_value++;  }  int Sum(int x, int y) {     inc_sum_counter();     return (x+y);  }  我们可通过对全局变量 sum_value添加锁保护,使得 sum_counter()成为一个线程安全的函数。 例3: static int sum_value= 0;  pthread_mutex_t lock= PTHREAD_MUTEX_INITIALIZER;  void sum_counter(int i, int j) {     pthread_mutex_lock( &lock );     sum_value++;     pthread_mutex_unlock( &lock );  }     int sum(int x, int y) {     sum_counter();     return (x+y);  }     现在 , sum()和 sum_counter()都成为了线程安全函数。在多线程环境下,sum()可以被并发的调用,但所有访问sum_counter()线程都会在互斥锁 lock上排队,任何一个时刻都只允许一个线程修改sum_value,所以sum_value()就是线程安全的。

二、可重入函数

除了线程安全还有一个很重要的概念就是 可重入(Re-entrant),所谓可重入,即:当一个函数在被一个线程调用时,可以允许被其他线程再调用。

可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

可重入函数也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。

显而易见,如果一个函数是可重入的,那么它肯定是线程安全的。但反之未然,一个函数是线程安全的,却未必是可重入的。程序开发人员应该尽量编写可重入的函数。

(1)一个函数想要成为可重入的函数,必须满足下列要求:

a) 不能使用静态或者全局的非常量数据

b) 不能够返回地址给静态或者全局的非常量数据

c) 函数使用的数据由调用者提供

d) 不能够依赖于单一资源的锁

e) 不能够调用非可重入的函数

(2)不可重入函数:
    a)函数中使用了静态变量,无论是全局静态变量还是局部静态变量。 
    b)函数返回静态变量。 
    c)函数中调用了不可重入函数。
    d)函数体内使用了静态的数据结构;
    e)函数体内调用了malloc()或者free()函数;
    f)函数体内调用了其他标准I/O函数。
    g)函数是singleton中的成员函数而且使用了不使用线程独立存储的成员变量 。
总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。

对比前面的要求,例1的 sum()函数是可重入的,因此也是线程安全的。例3中的 sum_counter()函数虽然是线程安全的,但是由于使用了静态变量和锁,所以它是不可重入的。因为 例3中的 sum()使用了不可重入函数 sum_counter(), 它也是不可重入的。

注意事项:

编写可重入函数时,若使用全局变量,则应通过关中断、互斥信号量(即P、V操作)等手段对其加以保护。

若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

时间: 2024-08-02 15:13:48

线程安全与可重入函数的相关文章

线程安全和可重入函数的区别与联系

线程安全: 一般来说,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果.就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者数据污染. 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和 运行的结果是一样的,而且其他的变量的值也和预期的是一样  的, 就是线程安全的. 或者说:一个类或者程序所提供的接口对于线程来说

线程安全与可重入函数之间的区别与联系

线程安全: 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和 运行的结果是一样的,而且其他的变量的值也和预期的是一样  的, 就是线程安全的. 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题. 线程安全问题都是由全局变量及静态变量引起的. 若每个线程中对全局变量.静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线程同时执行

线程安全和可重入函数

一.线程安全 1.线程安全函数:C语言中局部变量是在栈中分配的,任何未使用静态数据或其他共享资源的函数都是线程安全的. (1)对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量.局部静态变量.分配于堆的变量都是共享的,即是非线程安全的. (2) 在对这些共享变量进行访 问时,如果要保证线程安全,则必须通过加锁的方式. 2.线程安全的:                   如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的.                  

线程安全和可重入函数之间的区别和联系

概念 重入:即重复调用,函数被不同的流调用,有可能会出现第一次调用还没返回时就再次进入该函数开始下一次调用. 可重入:当程序被多个线程反复执行,产生的结果正确. 如果一个函数只访问自己的局部变量或参数,称为可重入函数. 不可重入:当程序被多个线程反复调用,产生的结果出错. 当函数访问一个全局的变量或者参数时,有可能因为重入而造成混乱,像这样的函数称为不可重入函数. 线性安全:一般来说,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果. 可重入函数与线程安全的区别

线程安全与可重入函数的区别及联系

一.线程安全 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的.  或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题. 线程安全问题都是由全局变量及静态变量引起的. 若每个线程中对全局变量.静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线程同时执

线程安全与可重入函数的区别与联系

一. 线程安全 前面提到过线程的同步与互斥,也就是当两个线程同时访问到同一个临界资源的时候,如果对临界资源的操作不是原子的就会产生冲突,使得结果并不如最终预期的那样,比如如下的程序: #include <stdio.h> #include <pthread.h> int g_val = 0; void* fun(void *arg) {     int i = 0;     while(i++ < 500)     {            int tmp = g_val;

线程安全与可重入函数的联系与区别

轻量级进程(LWP)是计算机操作系统中一种实现多任务的方法. 在计算机操作系统中,轻量级进程(LWP)是一种实现多任务的方法.与普通进程相比,LWP与其他进程共享所有(或大部分)它的逻辑地址空间和系统资源:与线程相比,LWP有它自己的进程标识符,优先级,状态,以及栈和局部存储区,并和其他进程有着父子关系:这是和类Unix操作系统的系统调用vfork()生成的进程一样的.另外,线程既可由应用程序管理,又可由内核管理,而LWP只能由内核管理并像普通进程一样被调度.Linux内核是支持LWP的典型例子

线程安全和可重入函数的区别和联系

线程安全函数 概念:        线程安全的概念比较直观.一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果.  确保线程安全:        要确保函数线程安全,主要需要考虑的是线程之间的共享变量.属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器.因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量.局部静态变量.分配于堆的变量都是共享的.在对这些共享变量进行访问时,如果要保证线程安全

可重入函数与线程安全的区别和联系

1.可重入函数 可重入函数即表示可以被多个执行流重复进入,意味着只使用自己栈上的变量,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰. 一个不可重入函数的例子: int global; int fun( int a ) { int temp; global = a; temp = gloabl*2; return temp; } global是一个全局变量,若进程a运行这段代码传入的参数是2,预期的结果是4:进程b也运行这段代码,传入的参数是3,由于操作系统的进程调