码迷,mamicode.com
首页 > 编程语言 > 详细

线程安全与可重入函数

时间:2016-05-05 22:50:04      阅读:905      评论:0      收藏:0      [点我收藏+]

标签:线程安全与可重入函数

一、线程安全

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


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


        线程安全 (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操作)等手段对其加以保护。

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



本文出自 “流苏” 博客,请务必保留此出处http://ab6107.blog.51cto.com/10538332/1770515

线程安全与可重入函数

标签:线程安全与可重入函数

原文地址:http://ab6107.blog.51cto.com/10538332/1770515

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!