Linux----线程安全和可重入函数

一、线程安全

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

        线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

       前面提到过线程的同步与互斥,也就是当两个线程同时访问到同一个临界资源的时候,如果对临界资源的操作不是原子的就会产生冲突,使得结果并不如最终预期的那样,比如如下的。

       程序实现:

#include <stdio.h>
#include <pthread.h>

int g_val = 0;

void* fun(void *arg)
{
    int i = 0;
    while(i++ < 500)
    {   
        int tmp = g_val;
        printf("thread is :%u        g_val: %d\n", pthread_self(), g_val);
        g_val = tmp + 1;; 
    }   
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, fun, NULL);
    pthread_create(&tid2, NULL, fun, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("g_val: %d\n", g_val);

    return 0;
}
运行结果:

       上面创建了两个线程,我们知道,同一个进程中的线程之间对进程的资源是共享的,当两个线程进入同一个临界区将一个全局变量g_val的值进行不断地加1的时候,因为这个操作过程并不是原子的,因此就会产生访问冲突,本来我们是想让两个线程对g_val各自加500次,最终结果想要输出1000,但运程程序我们会发现结果并不如我们预期的那样,而且每一次运行的结果都不一样;因此,这种代码我们称作不是线程安全的。

       因此,线程安全是指当多个线程访问同一个区域的时候其最终的结果是可预期的,并不会因为产生冲突或者异常中断再次恢复而使结果不可预期。

二、可重入函数

       可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
        可重入函数不为连续的调用持有静态数据;不返回指向静态数据的指针;所有数据都由函数的调用者提供;使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。绝不调用任何不可重入函数。

        程序例子:

#include <stdio.h>
#include <signal.h>

int g_val = 0;

void fun()
{
    int i = 5;
    while(i--)
    {   
        g_val++;
        printf("g_val: %d\n", g_val);
        sleep(1);
    }   
}

int main()
{
    signal(2, fun);
    fun();
    printf("when the fun end, g_val: %d\n", g_val);

    return 0;
}

运行结果:


       上面的程序中首先在main函数中注册了一个函数fun,signal函数意思是当收到2号信号时执行自定义的函数也就是fun,而2号函数是由Ctrl+c产生的SIGINT信号,该信号的默认处理动作是终止进程,这里我们捕捉它;设置了一个全局变量g_val,fun函数的功能是每隔1秒将g_val进行加1,最终是想让g_val的值为5。

       上面的结果中,当g_val加到5的时候,键入Ctrl+c,这时进程会收到2号信号,而2号信号会被捕捉到执行fun函数,因为g_val是全局变量,因此执行捕捉信号的函数会继续在全局变量g_val上相加,因此,结果为10。

       为了使结果并不因为中断而改变,可将程序改为如下:

#include <stdio.h>
#include <signal.h>

void fun()
{
    int g_val = 0;
    int i = 5;
    while(i--)
    {   
        val++;
        printf("g_val: %d\n", g_val);
        sleep(1);
    }
}

int main()
{
    signal(2, fun);
    fun();

    return 0;
}
运行结果:

       结果中,当函数两次被调用的时候,两次结果最终并不影响且值相等;所以,上面的程序中,函数fun是可重入函数。
      一个可重入函数需要满足的是:

              a、不使用全局变量或静态变量;

              b、不使用用malloc或者new开辟出的空间;

              c、不调用不可重入函数;

       其实总结就是,一个可重入函数内部使用的数据都应该来自于自身的栈空间,包括返回值也不应该是全局或者静态的;而正是因为其中的操作数据都来自于自身的栈空间,而每次调用函数会开辟不同的栈空间,因此二者互不影响。

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

       函数可以是可重入的,是线程安全的,或者二者皆是,或者二者皆非。不可重入的函数不能由多个线程使用。另外,或许不可能让某个不可重入的函数是线程安全的。

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

        1、可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。

  2、线程安全是在多个线程情况下引发的,而可重入函数可以在只有一个线程的情况下来说。

  3、线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

  4、如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

  5.如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

  6、线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。

      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值