【问题标题】:HOW are local static variables thread unsafe in c?c语言中的局部静态变量线程如何不安全?
【发布时间】:2018-06-11 10:50:10
【问题描述】:

所以环顾互联网,我找不到关于此的一致且有用的信息。那么问题来了:

为什么说 C 中的局部静态变量是线程不安全的?我的意思是,静态局部变量存储在数据段中,由所有线程共享,但是内部链接不应该阻止线程进入彼此的静态变量吗?

This 论坛帖子似乎暗示线程实际上偶尔会跨入彼此的数据段,但这种行为不会明显违反自 90 年代以来的所有 c 标准吗?如果这种行为是意料之中的,那么数据段(即所有具有静态存储持续时间的变量,包括全局变量)的使用是否早就在后续的 c 标准中被弃用了?

我真的不明白这一点,因为每个人似乎都反对局部静态变量,但人们似乎无法就原因达成一致,研究一些论点表明他们是错误的。

一方面,我认为局部静态变量是在函数调用之间传递信息的一种非常好的方式,它可以真正提高可读性和限制范围(例如,将信息作为参数传递并将其写回每个函数调用)。

据我所知,局部静态变量的使用完全合法。但也许我错过了什么?我真的很想知道是不是这样。

[编辑]:这里的答案很有帮助。感谢大家的洞察力。

【问题讨论】:

  • 链接帖子中的一些答案只是废话。这就是为什么你不应该信任 Quora - 改用 Stack Overflow。
  • 它们怎么可能不安全?
  • @Lundin 在阅读了您的评论后,我确实打开了大声笑的链接..:)

标签: c multithreading static thread-safety


【解决方案1】:

但是内部链接不应该阻止线程进入彼此的静态变量吗?

不,链接与线程安全无关。它只是限制函数访问在其他范围内声明的变量,这是一个不同且不相关的问题。

假设你有这样一个函数:

int do_stuff (void)
{
  static int x=0;
  ...
  return x++;
}

然后这个函数被多个线程调用,线程1和线程2。线程回调函数不能直接访问x,因为它有本地作用域。但是,他们可以拨打do_stuff() 并且可以同时拨打电话。然后你会得到这样的场景:

  • 线程 1 已执行 do_stuff,直到将 0 返回给调用者。
  • 线程 1 即将将值 1 写入 x,但在此之前......:
  • 上下文切换,线程2步入并执行do_stuff
  • 线程2读取x,它仍然是0,所以它返回0给调用者,然后将x增加1
  • x 现在是 1
  • 线程 1 再次获得焦点。它即将将1 存储到x,所以它就是这样做的。
  • 现在x 仍然是1,尽管如果程序正常运行,它应该是2

当对x 的访问在多条指令中完成时,情况会变得更糟,以至于一个线程读取“x 的一半”然后被中断。

这是一个“竞争条件”,这里的解决方案是使用互斥锁或类似的保护机制来保护x。这样做会使函数线程安全。或者,do_stuff 可以重写为不使用任何静态存储变量或类似资源 - 然后它将是 re-entrant

【讨论】:

  • 是的,你说的很有道理。我从来没有想过在不同的线程中使用相同的代码,这可能是我没有想到这一点的原因。感谢您的洞察力。
  • @FaresGargouri 同样的问题经常存在于库代码中,例如 C 标准库的实现。这就是为什么我们必须在从多个线程调用一个函数之前知道它是否是线程安全的。如果它没有被记录为线程安全,它可能不是。
  • 除了x的最终值不正确之外,还有一个问题是do_stuff的两次交错调用可能返回相同的值(如果它的名称不是do_stuff,这显然是一个大问题但是,比如说get_unique_id)。
  • @SteveSummit 这种态度是无数bug的根源。许多糟糕的程序员侥幸逃脱,因为特定的编译器碰巧从他们的 C 代码中生成原子指令。也不能保证return ret 是原子的,也不能保证a = b 是原子的。我们总是收到一些愚蠢的评论,比如“我只从一个地方写,所以不需要互斥锁”。持这种态度的人还没有掌握C代码如何翻译成机器代码。让他们蒙在鼓里对任何人都没有好处。
【解决方案2】:

内部链接不应该阻止线程进入彼此的静态变量吗?

链接与并发无关:内部链接阻止翻译单元而不是线程查看彼此的变量。

一方面,我认为局部静态变量是在函数调用之间传递信息的一种非常好的方式,它可以真正提高可读性并限制范围

通过静态变量在调用之间传递信息与通过全局变量传递信息没有太大区别,原因相同:当你这样做时,你的函数变得不可重入,严重限制了它的使用。

问题的根本原因在于,通过静态链接对变量的读/写使用将函数形式从无状态转换为有状态。如果没有静态变量,函数控制的任何状态都必须从外部传递给它;另一方面,静态变量让函数保持“隐藏”状态。

要查看保持隐藏状态的后果,请考虑strtok 函数:您不能同时使用它,因为多个线程会相互踩踏彼此的状态。此外,如果您希望从当前正在解析的字符串中解析每个标记,则即使在单个线程中也不能使用它,因为您的二级调用会干扰您自己的顶级调用。

【讨论】:

  • 关于第一点:那么当变量存储在数据段中时,线程是否有可能只是随机访问不在其范围内的变量?至于第二个:好点,但是当谈论那些只是为了模块化代码块的函数时呢? 否则太大和笨重,而不是代码重用作为一个库?
  • @FaresGargouri 就 C 标准而言,线程不存在,更不用说有范围了。如果两个线程可以调用一个修改静态变量的函数,那么这两个线程可以随机踩对方的数据。线程本地存储由线程库实现。就模块化块而言,您可以在不破坏可重用性的情况下做到这一点,方法是为数据提供不透明的句柄,否则这些句柄会以静态变量结尾,并让用户将这些句柄传递给模块中的每个函数。
  • 使用不透明句柄听起来是个有趣的建议。您能否链接一些有关如何实现这一点的文献?
  • @FaresGargouri 这是一个很好的起点:What is an opaque pointer in C?
  • @Lundin 很高兴知道,谢谢。我在 C99 参考中寻找关键字“线程”,但没有找到。我想 12 年确实有所作为 ;-)
【解决方案3】:

在我看来,基础是错误的,或者至少,它与任何其他糟糕的设计一样不安全。

一个糟糕的软件实践(或线程不安全)可能是在没有标准或保护类型的情况下共享资源(线程之间有不同且很好的通信方式,例如队列、邮箱等,或者信号量和互斥锁,如果资源有共享),但这是开发人员的错,因为他们没有使用正确的机制。

实际上我看不出你的意思,一个静态局部变量,它的范围定义得很好(更好的是,对于嵌入式应用程序来说,避免内存溢出很有用)并且不能从中访问,所以我想没有关系在不安全代码和静态局部变量之间(或者至少,不是一般意义上的)。

如果您谈论的是可以在没有保护的情况下(通过回调或其他方式)从两个不同的任务中写入/读取/.. 的静态局部变量,那是一个可怕的设计(同样是开发人员的错),但是不,因为静态局部变量(通常)是不安全的。

【讨论】:

  • 好的,所以我从中得出的结论是,不,静态局部变量本身并不是坏事,但如果代码由不同的线程使用,则需要共享资源管理(例如通过信号量) .
【解决方案4】:

同时读取和写入任何非原子对象的行为在 C 中是未定义的

static 变量使这种情况发生的可能性大大高于自动或动态变量。这就是问题的症结所在。

因此,如果您不控制线程(例如使用互斥单元),您可能会将程序置于未定义状态。

一种中途休息; 线程本地存储在某些 C 编译器中可用,但尚未纳入 C 标准(参见 C++11 的thread_local)。例如,请参阅https://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Thread-Local.html

【讨论】:

  • 但是如果变量不在当前范围内,程序会尝试访问它吗?
  • @FaresGargouri:不,你只需要避免从多个线程读取/写入。
  • 好的,所以这只有在两个或多个线程使用相同的代码块时才有可能,据我所知,这实际上很有意义。
  • C11 支持_Thread_local。是否有编译器实现它是另一回事。
【解决方案5】:

内部链接不应该阻止线程进入彼此的静态变量吗?

您的问题被标记为。 C 编程语言中没有线程。如果您的程序创建任何新线程,它会通过在运行时调用某个库来实现。 C 工具链不知道线程是什么,它无法知道您调用的库例程创建线程,并且它无法知道您是否认为任何特定的静态变量由一个线程或另一个线程“拥有”线程。

程序中的每个线程都与其他线程在相同的虚拟地址空间中运行。每个线程可能都可以访问任何其他线程可以访问的所有相同变量。如果程序中的一个变量实际上多个线程使用,则程序员有责任(而不是工具链的责任)确保线程在一个线程中使用它安全的方式。

似乎每个人都反对局部静态变量,

在团队中工作以开发大型、长期软件系统(想想数万年和数十万到数千万行代码)的软件开发人员有一些非常清楚的理由来避免使用静态变量。不是每个人都在这样的系统上工作,但你会在这里遇到一些人。

人们似乎无法就原因达成一致

并不是所有的软件系统都需要几十年的维护和升级,也不是所有的软件系统都有几千万行代码。这是一个很大的世界。有些人出于许多不同的原因编写代码。他们的需求不尽相同。

研究一些论点表明它们是错误的

有些人出于许多不同的原因编写代码......对您来说似乎“构思不当”的事情可能是其他一些开发人员长期思考过的事情。也许您没有完全了解他们的 需求。

据我所知,局部静态变量的使用完全合法

是的。这就是它们存在的原因。与许多其他编程语言一样,C 编程语言是一种通用工具,可以以多种不同方式使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多