≡菜单

C线程安全和可重入函数示例

重入和线程安全是可以与良好的编程习惯相关联的两个不同的概念。在本文中,我们将借助一些代码片段尝试并理解概念及其差异。

1.线程安全代码

顾名思义,当多个线程可以执行同一代码而不会引起同步问题时,一段代码是线程安全的。让我们看下面的代码片段:

...
...
...

char rr [10];
int 指数=0;

int func(char c)
{
    int i=0;
    如果(索引>= sizeof(arr))
    {
        printf("\ n没有存储空间\ n");
        返回-1;
    }
    rr [index] = c;
    指数++;
    return 指数;
}

...
...
...

上面的函数填充数组‘arr’将字符值作为参数传递给它,然后更新‘index’ variable so that subsequent calls to this function write 上 the updated 指数 of the rr ay.

假设两个线程正在使用此函数。现在,假设线程一调用此函数并使用值更新数组索引‘c’。现在,在更新‘index’ suppose second thread gets the execution control and it also calls this function. Now since the 指数 was not updated 通过 thread 上 e , so this thread writes 上 the same 指数 and hence overwrites the value written 通过 thread 上 e.

因此,我们看到线程之间缺乏同步是此问题的根本原因。

现在,让此函数线程安全:

...
...
...

char rr [10];
int 指数=0;

int func(char c)
{
    int i=0;
    如果(索引>= sizeof(arr))
    {
        printf("\ n没有存储空间\ n");
        返回-1;
    }

    /* ...
       Lock a mutex 这里
       ...
    */

    arr[index] = c;
    指数++;

    /* ...
       unlock the mutex 这里
       ...
    */

    return 指数;
}

...
...
...

What we did above is that we made the rr ay and 指数 updates an atomic operation using the mutex locks. Now even if multiple threads are trying to use this function, there would be no synchronization problems as any thread which acquire the mutex will complete both the operations (array and 指数 update) before any other thread acquires the mutex.

因此,上面的代码现在变为线程安全的。

2.重入代码

可重入代码的概念与线程安全代码略有不同。通常,如果在一个执行线程中调用了一个函数,则在该特定函数执行完成之前,流程就无法前进。但是,在某些情况下,在单个线程中也可以通过再次调用同一函数来中断函数的执行。因此,可以成功处理这种情况的一段代码称为重入代码。让我们看下面的例子:

...
...
...

char *s;

void func()
{
    int new_length = 0;

    // initialize 'new_length'
    // with some new value 这里

    char *ptr = realloc(s, new_length);

    if(ptr)
    {
        s = ptr;
    }
    else
    {
        //Report Failure
    }

    // do some stuff 这里
}

...
...
...

如果我们分析上述代码的重入能力,我们发现该代码不是可重入的。这是因为上述代码在以下方面存在缺陷:如果信号处理程序使用相同的函数(响应某些信号的处理),则在调用函数func() realloc()和‘if’条件旁边,然后信号处理程序对此函数的调用将中断执行。在这种情况下‘s’不会使用新分配的地址更新,因此重新分配可能会失败(或者程序甚至可能崩溃)。

因此,我们看到上面的代码不是可重入的。重入代码最不希望与全局变量一起使用。以下是重入代码的示例:

...
...
...

int exchange_values(int *ptr1, int *ptr2)
{
    int tmp;

    tmp = * ptr1;
    * ptr1 = * ptr2;
    * ptr2 = * tmp;

    return 0;
}

...
...
...

3.线程安全但不可重入

一段代码可以是线程安全的,但是’不必重新输入。看下面的代码:

...
...
...

int func()
{
    int ret = 0;

    // Lock Mutex 这里

    // Play with some
    // global data structures
    // 这里   

    // Unlock mutex

    return ret;
}

...
...
...

在上面的示例中,由于关键部分受互斥锁保护,因此上面的代码是线程安全的,但不可重入,因为如果通过某些信号处理程序中断了上述函数的执行(在处理信号时调用同一函数)然后(如果正在使用非递归互斥锁)则中断第一个执行,而第二个执行将永远等待获取互斥锁。因此,整个程序总体上将挂起。

4.重入但不是线程安全的

重入是与某个函数相关的东西,该函数的第一次执行被第二次调用(从同一线程内)中断,并且第一次执行在第二次执行完成时恢复。线程可以继续踩到另一个线程的情况并非如此’脚趾多次。因此,如果函数是可重入的,则不能保证其线程安全。

如果您喜欢这篇文章,您可能还会喜欢..

  1. 50个Linux Sysadmin教程
  2. 50个最常用的Linux命令(包括示例)
  3. 排名前25位的最佳Linux性能监视和调试工具
  4. 妈妈,我找到了! – 15个实用的Linux Find命令示例
  5. Linux 101 Hacks第二版电子书 Linux 101黑客手册

Bash 101 Hacks书 Sed和Awk 101黑客手册 Nagios Core 3书 Vim 101黑客手册

{ 14 评论… 加一 }

  • 克龙 2012年7月27日,上午10:02

    那是一三吗?

    还有一个例子,对于第四个例子,可能是重要/急需的例子,只是我的看法

  • 贾拉尔·哈吉霍拉玛利 2012年7月27日,上午11:49

    你好

    非常感谢….

  • R @ OUL 2012年7月29日,上午3:28

    The first thread safe code will segfault, testing var 指数 is critical 这里.

  • 鲍勃 2012年7月30日,上午8:13

    您提到了非递归互斥体。可能对解释递归/非递归互斥锁之间的区别很有用。通常,在同一示例中混合太多概念不是一个好主意。像往常一样出色的工作。

  • 亨利·德·费洛迪 2012年7月31日,下午12:37

    您 say
    “通常,如果在一个执行线程中调用了一个函数,则在该特定函数执行完成之前,流程就无法前进。”
    你能解释为什么吗?

  • 凯文·考克斯 2012年7月31日,下午3:20

    The first chunk of code has an off 通过 上 e overflow error (you are killing the fairies). 您 should lock the mutex before the 指数 check or else you will overflow your buffer.

  • 卡塔林 2012年8月1日,上午5:32

    辛苦了我喜欢你的文章。
    您 can also read something about mutex 这里.

  • 马约尔 2012年8月2日,上午5:31

    ———————————————————–
    int tmp;

    tmp = * ptr1;
    * ptr1 = * ptr2;
    * ptr2 = * tmp;
    —————————————————————
    这是正确的吗 ?

  • 里克·斯坦利 2012年8月6日,上午9:12

    “int tmp;

    tmp = * ptr1;
    * ptr1 = * ptr2;
    * ptr2 = * tmp;”

    应该读:

    int tmp;

    tmp = * ptr1;
    * ptr1 = * ptr2;
    * ptr2 = tmp;

    tmp是一个int,而不是指向int的指针。

  • 疯狂的 2012年8月18日,晚上8:26

    嗨!我很高兴阅读您的文章。但是我对可重入函数有一些疑问。身为你的“4.重入但不是线程安全的” said “因此,如果函数是可重入的,则不能保证其线程安全。”。但据我所知,可重入函数必须是线程安全的,能否给我一些代码解释“4.重入但不是线程安全的” ? Thanks

  • 拉古 2012年8月31日,下午2:00

    很好的描述。但是第一个线程安全示例(使其成为线程安全后)存在一些问题。凯文已经指出了其中之一。但是最后一条陈述也有问题。

    本质上,代码的编写方式不能使函数成为线程安全的(实际上是函数的任何部分)。不仅更新必须在关键区域内。只要函数中存在对变量的读写操作,整个区域(从变量的第一个参考到最后一个参考)都必须成为关键区域。

    但是可以对其稍加修改以使其具有线程安全性。我相信下面更正的代码使其具有线程安全性。

    这是修改后的代码(不优雅,因为有2个解锁和1个锁-
    (未实际编译;但是您知道了):

    char rr [10];
    int 指数=0;

    int func(字符c)
    {
    int i = 0,tmp;

    / *在此处锁定互斥锁* /

    tmp = 指数;

    如果(索引>= sizeof(arr))
    {
    printf(“\n No storage\n”);
    / *在此处解锁互斥锁* /
    返回-1;
    }
    指数++;
    / *在此处解锁互斥锁* /

    rr [tmp] = c;

    返回tmp;
    }

  • Bsks 2012年10月24日,上午3:59

    你好,

    您确定函数是可重入的吗?
    您’重新处理指针,以便线程或进程都可以访问和修改ptr1和ptr2。

    我发现您有两次调用此函数的情况,并且没有’不能达到预期的效果。

  • 拉吉 2013年9月13日,上午3:56

    很好的文章…第四种情况的例子将使其完美…too much to expect…:)

  • 埃弗里特 2015年5月12日,晚上9:28

    在函数exchange_values中
    int tmp;
    tmp = * ptr1;
    * ptr1 = * ptr2;
    (此处中断)不可重入
    * ptr2 = tmp;

发表评论