静态变量和线程(C语言)

19

我知道在C语言中在函数内声明一个静态变量意味着该变量会保留其状态,即在函数调用之间保持不变。在线程的上下文中,这是否意味着该变量将在多个线程之间保留其状态,还是在每个线程之间具有单独的状态?

以下是一道过去试卷题,我很难回答:

以下C函数旨在为其调用者分配唯一标识符(UID):

get_uid() 
{
static int i = 0;
return i++;
}

解释在多线程环境下,get_uid()可能出现什么错误。使用具体的示例场景,详细说明为什么会发生这种错误以及如何发生。

目前我假设每个线程都有一个独立的变量状态,但我不确定这是否正确,或者答案更多地与缺乏互斥有关。如果是这样,那么在这个例子中如何实现信号量?


你最后一个问题是关于如何在这个例子中实现信号量。你的意思是要问如何使用信号量来实现吗? - WhozCraig
1
如果您正在使用gcc,标准的一个不错的补充是静态__thread变量。它们是静态变量,但是每个线程都有自己的本地变量。我发现它特别有用于管理与服务器的线程本地连接... - user1251840
6个回答

25

你的假设(线程有自己的副本)是不正确的。代码的主要问题在于,当多个线程调用该函数 get_uid() 时,存在可能的 竞争条件,因为哪些线程增加了i并获取ID可能不唯一。


好的,那么同时进入该函数的两个线程将使 i 增加两次,然后以相同的值返回? - dahui
还有很多其他可能性。增量可能正在发生,最终你会得到一个看起来完全随机的i值。 - kanielc

17

一个进程中的所有线程共享相同的地址空间。由于i是一个静态变量,它有一个固定的地址。它的“状态”只是该地址处的内存内容,这些内容被所有线程共享。

后缀++运算符会增加其参数的值,并在递增之前产生该参数的值。执行它们的顺序未定义。一种可能的实现方式是:

copy i to R1
copy R1 to R2
increment R2
copy R2 to i
return R1

假如有多个线程正在运行,它们可以同时或交替执行这些指令。自己尝试各种顺序,看一下会得到哪些结果。 (请注意,即使是在同一个CPU上运行的线程,每个线程也有自己的寄存器状态,因为在线程切换时会保存和恢复寄存器。)

类似这样的情况,由于不同线程中操作的不确定排序而导致结果不同,被称为“竞态条件”,因为不同的线程之间存在“竞赛”,争夺哪个线程先执行哪个操作。


@user2253489 你可以通过点赞来奖励一个好的回答。如果你喜欢得够多,甚至可以改变你的选择。 - Jim Balter
1
你必须喜欢那些没有解释就给你点踩的人 :/ - dahui

5
不,如果你想要一个值取决于使用它的线程的变量,你应该看一下线程本地存储
静态变量,你可以将其想象成完全全局变量。这真的差不多。因此,它由整个系统共享,该系统知道其地址。 编辑:如评论所提醒的那样,如果您将此实现保留为静态变量,则竞争条件可能会使 i 在几个线程同时递增,这意味着您无法确定函数调用将返回哪个值。在这种情况下,您应该通过所谓的同步对象(例如互斥锁临界区)来保护访问。

1
代码示例还存在非原子更新问题,这意味着多个线程可能会导致单个增量。 - WhozCraig

1

由于这看起来像是作业,我只会回答其中的一部分,那就是每个线程将共享相同的i副本。也就是说,线程不会获得自己的副本。至于互斥位,请您自行处理。


这不是作业,而是为考试复习!不过我自己动手做可能会学到更多,所以谢谢。 - dahui

1
每个线程将共享同一个静态变量,这个变量很可能是全局变量。当一些线程的值错误时,情况就是竞争条件(增量不是在一个单独的执行中完成的,而是在3个汇编指令中完成的:加载、增量、存储)。在此处阅读,并且链接中的图表解释得很好。 竞争条件

1
如果你正在使用gcc,你可以使用原子内置函数。我不确定其他编译器是否可用。
int get_uid() 
{
    static int i = 0;
    return __atomic_fetch_add(&i, 1, __ATOMIC_SEQ_CST);
}

这将确保变量一次只能被一个线程操作。

1
这是有用的信息,但它不是问题的答案...应该是一条评论。 - Jim Balter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接