从全球结构中读取时,我是否需要信号量?

时间:2008-11-05 16:27:59

标签: c multithreading global-variables mutex semaphore

一个相当基本的问题,但我没有在任何地方看到它。

假设我们有一个全局结构(在C中),如下所示:

struct foo {
  int written_frequently1;
  int read_only;
  int written_frequently2;
};

我觉得如果我们有大量的线程读写,我们需要written_frequently成员的信号量(或其他锁定),即使是阅读,因为我们不能100%肯定对此结构的赋值将是原子的。

如果我们想要很多线程来读取read_only成员,而没有要写入,那么我们需要一个关于struct access的信号量只是为了阅读吗?

(我倾向于拒绝,因为不断更改前后位置的事实不会影响read_only成员,并且读取该值的多个线程不应相互干扰但我不确定。)


[编辑:我现在意识到我应该更好地问这个问题,以便澄清非常具体我的意思。当然,当我第一次提出这个问题时,我并没有真正理解所涉及的所有问题。当然,如果我现在全面编辑这个问题,我将毁掉所有这些伟大的答案。我的意思更像是:

struct bar {
  char written_frequently1[LONGISH_LEN];
  char read_only[LONGISH_LEN];
  char written_frequently2[LONGISH_LEN];
};

我问的主要问题是,由于这些数据是结构的一部分,它是否完全受其他结构成员的影响,并且它可能会影响它们作为回报?

成员是整体的,因此写作可能是原子的,在这种情况下真的只是一个红色的鲱鱼。]

10 个答案:

答案 0 :(得分:7)

您需要一个互斥锁来保证操作是原子操作。所以在这种特殊情况下,可能根本不需要互斥锁。具体来说,如果每个线程写入一个元素,则写入是原子的新值独立于任何元素(包括其自身)的当前值,没有问题。

示例:多个线程中的每一个都更新一个“last_updated_by”变量,该变量只记录更新它的最后一个线程。显然,只要变量本身以原子方式更新,就不会发生错误。


但是,如果线程一次读取或写入多个元素,那么需要一个互斥锁来保证一致性,特别是因为你提到锁定一个元素而不是整个结构

示例:一个主题更新结构的“日”,“月”和“年”元素。这必须以原子方式发生,以免另一个线程在“月”增量之后但在“日”包装到1之前读取结构,以避免诸如2月31日之类的日期。请注意,您必须兑现互斥锁时读;否则你可能会读到一个错误的,半更新的值。

答案 1 :(得分:6)

如果read_only成员实际上是只读的,则不存在数据被更改的危险,因此不需要同步。这可能是在线程启动之前设置的数据。

无论频率如何,您都希望同步任何可写入的数据。

答案 2 :(得分:4)

“只读”有点误导,因为变量在初始化时至少被写入一次。在这种情况下,如果它们位于不同的线程中,您仍然需要在初始写入和后续读取之间存在内存障碍,否则它们可能会看到未初始化的值。

答案 3 :(得分:3)

读者也需要互斥!

似乎有一种常见的误解,认为互斥体只适用于作者,读者不需要它们。 这是错误的,,这种误解导致极难诊断的错误。

这就是为什么,以一个例子的形式。

想象一下使用代码每秒更新的时钟:

if (++seconds > 59) {        // Was the time hh:mm:59?
   seconds = 0;              // Wrap seconds..
   if (++minutes > 59)  {    // ..and increment minutes.  Was it hh:59:59?
     minutes = 0;            // Wrap minutes..
     if (++hours > 23)       // ..and increment hours.  Was it 23:59:59?
        hours = 0;           // Wrap hours.
    }
}

如果代码不受互斥锁保护,则另一个线程可以在更新过程中读取hoursminutesseconds变量。遵循上面的代码:

[Start just before midnight] 23:59:59
[WRITER increments seconds]  23:59:60
[WRITER wraps seconds]       23:59:00
[WRITER increments minutes]  23:60:00
[WRITER wraps minutes]       23:00:00
[WRITER increments hours]    24:00:00
[WRITER wraps hours]         00:00:00

从第一个增量到最后一个操作六个步骤之后,时间无效。如果读者在此期间检查时钟,它将看到一个不仅可能不正确但非法的值。而且由于您的代码很可能依赖而没有直接显示时间,因此这是“跳弹”错误的经典来源,众所周知难以追踪。< / p>

修复很简单。

使用互斥锁环绕时钟更新代码,创建读取器功能在执行时也锁定互斥锁。现在,读者将等到更新完成,并且编写器不会更改读取中的值。

答案 4 :(得分:2)

没有

通常,您需要信号量来阻止对资源的并发访问(在这种情况下为int)。但是,由于read_only成员是只读的,因此在访问期间/之间不会发生变化。注意,它甚至不必是原子读取 - 如果没有任何变化,你总是安全的。

您最初如何设置read_only

答案 5 :(得分:1)

如果所有线程都只是读取,则不需要信号量。

答案 6 :(得分:1)

您可能喜欢在practical lock free programming上阅读这些论文中的任何一篇,或者只是剖析并理解所提供的片段。

答案 7 :(得分:0)

我会隐藏函数调用后面的每个字段。只写字段将具有信号量。只读只返回值。

答案 8 :(得分:0)

添加到之前的答案:

  1. 在这种情况下,自然同步范例是互斥,而不是信号量。
  2. 我同意您在readonly变量上不需要任何互斥锁。
  3. 如果结构的读写部分具有一致性约束,通常您需要一个互斥锁用于所有,以保持操作原子性。

答案 9 :(得分:0)

非常感谢所有伟大的回答者(以及所有伟大的答案)。

总结一下:

如果存在结构的只读成员(在我们的例子中,如果值设置一次,很久之前任何线程可能想要读取它),那么读取该成员的线程不需要锁,互斥锁,信号量或任何其他并发保护。

即使其他成员经常被写入,也是如此。不同变量都属于同一结构的事实没有区别。