对象数组,在一个线程中更新,在另一个线程中读取

时间:2021-12-31 21:00:24

Simplified the question to give a more clear representation of what I'm actually asking

简化了问题,以便更清楚地表达我的问题

I have two threads, call them A and B. They share one object of type Foo which has a field called Name and is stored in an array of type Foo[] at index 0. The threads will always access index 0 in a order which is guaranteed by the system already, so there is no race condition of thread B getting before thread A.

我有两个线程,分别称为A和b。它们共享一个Foo类型的对象,该对象有一个名为Name的字段,并存储在索引0的Foo[]类型数组中。线程将始终访问索引0,这是由系统已经保证的,因此线程a在线程a之前没有竞争条件。

The order is this.

顺序是这样的。

 // Thread A
 array[0].Name = "Jason";

 // Thread B
 string theName = array[0].Name

As I said this order is already guaranteed, there is no way for thread B to read the value before thread A

正如我所说,这个顺序已经得到了保证,线程B无法在线程A之前读取值

What I want to ensure is two things:

我想确保的是两件事:

  1. Both threads get the latest object at index 0.
  2. 两个线程都在索引0处获得最新的对象。
  3. Thread B always gets the latest value in the .Name field
  4. 线程B总是在.Name字段中获取最新的值

Marking Name as volatile is not an option, as the real objects are a lot more complex and even have custom structs which can't even have the volatile attribute attached to them.

将名称标记为volatile不是一个选项,因为真正的对象要复杂得多,甚至有自定义结构,甚至不能将volatile属性附加到它们上。

Now, satisfying 1 is easy (always getting the latest object), you can do a .VolatileRead:

现在,满足1很容易(总是得到最新的对象),你可以做一个。

 // Thread A
 Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
 obj.Name = "Jason";

 // Thread B
 Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
 string theName = obj.Name

Or you can insert a memory barrier:

或者你可以插入一个内存屏障:

 // Thread A
 array[0].Name = "Jason";
 Thread.MemoryBarrier();

 // Thread B
 Thread.MemoryBarrier();
 string theName = array[0].Name

So my question is: Is this enough to also satisfy condition 2? That I always get the latest value from the fields of the object I read out? If the object at index 0 has not changed, but the Name has. Will doing a VolatileRead or a MemoryBarrier on index 0 make sure all the fields IN the object at index 0 also get their latest values?

我的问题是,这是否也满足条件2?我总是从我读出的对象的字段中获取最新的值?如果索引0处的对象没有更改,但是名称已经更改。在索引0上做一个volatile ead或MemoryBarrier会不会确保在索引0处的对象中的所有字段都得到它们的最新值?

2 个解决方案

#1


2  

None of these solutions, lock or volatile will solve your problem. Because:

这些解决方案,锁或volatile都不能解决您的问题。因为:

  1. volatile ensures that variables changed by one thread are visible immediately to other threads operating on the same data (i.e. they are not cached) and also that operations on that variable are not reordered. Not really what you need.
  2. volatile确保由一个线程更改的变量对在相同数据上操作的其他线程(例如,它们不被缓存)是可见的,并且该变量上的操作不会被重新排序。不是你真正需要的。
  3. lock ensures that the write / read do not occur simultaneously but does not guarantee their order. It depends which thread acquired the lock first, which is non-deterministic.
  4. lock确保写/读不会同时发生,但不能保证它们的顺序。它取决于哪个线程首先获得锁,这是不确定的。

Therefore, if your flow is:

因此,如果您的流程是:

Thread A read Name
Thread A modify Name
Thread B read Name

exactly in that order, you will need to enforce it with an event (i.e. AutoresetEvent for example):

正是按照这个顺序,您将需要使用一个事件(例如AutoresetEvent)来执行:

//Thread A
foo[0].Name = "John"; // write value
event.Set(); // signal B that write is completed

//Thread B
event.WaitOne(); // wait for signal
string name = foo[0].Name; // read value

This guarantees that thread B does not read the Name variable until A has modified it.

这保证线程B在A修改名称变量之前不会读取它。

Edit: Ok, so you are sure that the above flow is respected. Since you are saying that you cannot declare the fields volatile, I recommend the use of Thread.MemoryBarrier() to introduce fences that enforce ordering:

编辑:好的,所以你确定上面的流程是受尊重的。既然您说您不能声明字段的易变性,我建议使用Thread.MemoryBarrier()来引入强制排序的篱笆:

//Thread A
foo[0].Name = "John"; // write value
Thread.MemoryBarrier();

//Thread B
Thread.MemoryBarrier();
string name = foo[0].Name; // read value

For more info, check this document: http://www.albahari.com/threading/part4.aspx

有关更多信息,请查看此文档:http://www.albahari.com/threading/part4.aspx

#2


0  

Locks will address the problem you have if I understand it correctly. This is because lock generates implicit (full) memory barriers around itself.

如果我理解正确的话,锁将解决您的问题。这是因为锁会在自身周围产生隐式(完全)的内存障碍。

You can also use memory barrier explicitly using Thread.MemoryBarrier. Read more here. Memory barrier effect can be quite hard to notice on x86, but on more relaxed ordering system such as PPC it is often substantial.

您还可以使用Thread.MemoryBarrier显式地使用内存屏障。点击这里了解更多内容。在x86上很难注意到内存障碍的影响,但是在更宽松的订购系统(如PPC)上,它通常是相当可观的。

#1


2  

None of these solutions, lock or volatile will solve your problem. Because:

这些解决方案,锁或volatile都不能解决您的问题。因为:

  1. volatile ensures that variables changed by one thread are visible immediately to other threads operating on the same data (i.e. they are not cached) and also that operations on that variable are not reordered. Not really what you need.
  2. volatile确保由一个线程更改的变量对在相同数据上操作的其他线程(例如,它们不被缓存)是可见的,并且该变量上的操作不会被重新排序。不是你真正需要的。
  3. lock ensures that the write / read do not occur simultaneously but does not guarantee their order. It depends which thread acquired the lock first, which is non-deterministic.
  4. lock确保写/读不会同时发生,但不能保证它们的顺序。它取决于哪个线程首先获得锁,这是不确定的。

Therefore, if your flow is:

因此,如果您的流程是:

Thread A read Name
Thread A modify Name
Thread B read Name

exactly in that order, you will need to enforce it with an event (i.e. AutoresetEvent for example):

正是按照这个顺序,您将需要使用一个事件(例如AutoresetEvent)来执行:

//Thread A
foo[0].Name = "John"; // write value
event.Set(); // signal B that write is completed

//Thread B
event.WaitOne(); // wait for signal
string name = foo[0].Name; // read value

This guarantees that thread B does not read the Name variable until A has modified it.

这保证线程B在A修改名称变量之前不会读取它。

Edit: Ok, so you are sure that the above flow is respected. Since you are saying that you cannot declare the fields volatile, I recommend the use of Thread.MemoryBarrier() to introduce fences that enforce ordering:

编辑:好的,所以你确定上面的流程是受尊重的。既然您说您不能声明字段的易变性,我建议使用Thread.MemoryBarrier()来引入强制排序的篱笆:

//Thread A
foo[0].Name = "John"; // write value
Thread.MemoryBarrier();

//Thread B
Thread.MemoryBarrier();
string name = foo[0].Name; // read value

For more info, check this document: http://www.albahari.com/threading/part4.aspx

有关更多信息,请查看此文档:http://www.albahari.com/threading/part4.aspx

#2


0  

Locks will address the problem you have if I understand it correctly. This is because lock generates implicit (full) memory barriers around itself.

如果我理解正确的话,锁将解决您的问题。这是因为锁会在自身周围产生隐式(完全)的内存障碍。

You can also use memory barrier explicitly using Thread.MemoryBarrier. Read more here. Memory barrier effect can be quite hard to notice on x86, but on more relaxed ordering system such as PPC it is often substantial.

您还可以使用Thread.MemoryBarrier显式地使用内存屏障。点击这里了解更多内容。在x86上很难注意到内存障碍的影响,但是在更宽松的订购系统(如PPC)上,它通常是相当可观的。