一个相当基本的问题,但我没有在任何地方看到它。
假设我们有一个全局结构(在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];
};
我问的主要问题是,由于这些数据是结构的一部分,它是否完全受其他结构成员的影响,并且它可能会影响它们作为回报?
成员是整体的,因此写作可能是原子的,在这种情况下真的只是一个红色的鲱鱼。]
答案 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.
}
}
如果代码不受互斥锁保护,则另一个线程可以在更新过程中读取hours
,minutes
和seconds
变量。遵循上面的代码:
[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)
添加到之前的答案:
答案 9 :(得分:0)
非常感谢所有伟大的回答者(以及所有伟大的答案)。
总结一下:
如果存在结构的只读成员(在我们的例子中,如果值设置一次,很久之前任何线程可能想要读取它),那么读取该成员的线程不需要锁,互斥锁,信号量或任何其他并发保护。
即使其他成员经常被写入,也是如此。不同变量都属于同一结构的事实没有区别。