C#更新对象引用和多线程
读了这么多关于如何做的书后,我很困惑 下面是我想做的: 我有一个保存各种信息的数据结构/对象。我踏着数据结构,好像它是不可变的。每当我需要更新信息时,我都会制作一个DeepCopy并对其进行更改。然后交换旧对象和新创建的对象 现在我不知道如何把每件事都做好 让我们从读卡器/消费者线程的角度来看它C#更新对象引用和多线程,c#,multithreading,reference,garbage-collection,thread-safety,C#,Multithreading,Reference,Garbage Collection,Thread Safety,读了这么多关于如何做的书后,我很困惑 下面是我想做的: 我有一个保存各种信息的数据结构/对象。我踏着数据结构,好像它是不可变的。每当我需要更新信息时,我都会制作一个DeepCopy并对其进行更改。然后交换旧对象和新创建的对象 现在我不知道如何把每件事都做好 让我们从读卡器/消费者线程的角度来看它 MyObj temp = dataSource; var a = temp.a; ... // many instructions var b = temp.b; .... 据我所知,阅读参考文献是原
MyObj temp = dataSource;
var a = temp.a;
... // many instructions
var b = temp.b;
....
据我所知,阅读参考文献是原子的。因此,我不需要任何volatile或锁定来将数据源的当前引用分配给temp。但是垃圾收集呢。据我所知,GC有某种引用计数器,可以知道何时释放内存。所以,当另一个线程恰好在数据源被分配给temp时更新数据源时,GC是否会增加右侧内存块上的计数器?
另一件事是编译器/CLR优化。我将数据源分配给temp,并使用temp访问数据成员。CLR做什么?它是真的复制了dataSource还是优化器只是使用dataSource访问.a和.b?让我们假设temp.a和temp.b之间有很多指令,因此对temp/dataSource的引用不能保存在CPU寄存器中。那么temp.b真的是temp.b吗?或者它是针对dataSource.b进行优化的,因为复制到temp是可以优化的。如果另一个线程更新数据源以指向新对象,这一点尤其重要
我真的需要volatile、lock、readwriterlocksim、Thread.MemoryBarrier或其他东西吗?
对我来说重要的是,我想确保temp.a和temp.b访问旧的数据结构,即使另一个线程将数据源更新为另一个新创建的数据结构。我从不更改现有结构中的数据。更新总是通过创建副本、更新数据,然后更新对datastructre新副本的引用来完成
也许还有一个问题。如果我不使用volatile,那么需要多长时间才能让所有CPU上的所有内核看到更新的引用
说到挥发性,请看这里:
我做了一些测试程序:
namespace test1 {
public partial class Form1 : Form {
public Form1() { InitializeComponent(); }
Something sharedObj = new Something();
private void button1_Click(object sender, EventArgs e) {
Thread t = new Thread(Do); // Kick off a new thread
t.Start(); // running WriteY()
for (int i = 0; i < 1000; i++) {
Something reference = sharedObj;
int x = reference.x; // sharedObj.x;
System.Threading.Thread.Sleep(1);
int y = reference.y; // sharedObj.y;
if (x != y) {
button1.Text = x.ToString() + "/" + y.ToString();
Update();
}
}
}
private void Do() {
for (int i = 0; i < 1000000; i++) {
Something someNewObject = sharedObj.Clone(); // clone from immutable
someNewObject.Do();
sharedObj = someNewObject; // atomic
}
}
}
class Something {
public Something Clone() { return (Something)MemberwiseClone(); }
public void Do() { x++; System.Threading.Thread.Sleep(0); y++; }
public int x = 0;
public int y = 0;
}
}
namespace test1{
公共部分类Form1:Form{
公共表单1(){InitializeComponent();}
Something sharedObj=新事物();
私有无效按钮1\u单击(对象发送者,事件参数e){
线程t=新线程(Do);//启动一个新线程
t、 Start();//正在运行WriteY()
对于(int i=0;i<1000;i++){
参考某物=sharedObj;
int x=reference.x;//sharedObj.x;
系统线程线程睡眠(1);
int y=reference.y;//sharedObj.y;
如果(x!=y){
button1.Text=x.ToString()+“/”+y.ToString();
更新();
}
}
}
私人无效Do(){
对于(int i=0;i<1000000;i++){
someNewObject=sharedObj.Clone();//从不可变对象克隆
someNewObject.Do();
sharedObj=someNewObject;//原子
}
}
}
分类{
public Something Clone(){return(Something)MemberwiseClone();}
public void Do(){x++;System.Threading.Thread.Sleep(0);y++;}
公共整数x=0;
公共整数y=0;
}
}
在Button1\u中,单击有一个for循环,在for循环中,我使用直接“shareObj”和临时创建的“reference”访问数据结构/对象。使用引用足以确保“var a”和“var b”使用来自同一对象的值进行初始化
我唯一不明白的是,为什么“Something reference=sharedObj;”没有被优化掉,“int x=reference.x;”没有被“int x=sharedObj.x;”取代
编译器,jitter,怎么知道不优化这个呢?或者,对象在C#中暂时没有优化过
但最重要的是:我的示例之所以按预期运行,是因为它是正确的,还是只是意外地按预期运行
据我所知,阅读参考文献是原子的
对。但这是一个非常有限的财产。这意味着阅读参考资料会起作用;你永远不会得到半个旧引用的比特与半个新引用的比特混合,从而导致一个不工作的引用。如果有一个同时发生的变化,它对你是否得到旧的或新的参考没有任何承诺(这样的承诺意味着什么?)
因此,我不需要任何volatile或锁定来将数据源的当前引用分配给temp
也许吧,尽管在某些情况下这可能会有问题
但是垃圾收集呢。据我所知,GC有某种引用计数器,可以知道何时释放内存
不对。.NET垃圾收集中没有引用计数
如果存在对对象的静态引用,则该对象不符合回收条件
如果存在对对象的活动本地引用,则该对象不可用于回收
如果对象的字段中存在不符合回收条件的对象引用,则该对象也不符合回收条件(递归)
这里不算。要么存在禁止开垦的积极有力的参考,要么没有
这有很多非常重要的影响。这里的相关性在于,永远不会有任何不正确的引用计数,因为没有引用计数。强引用不会消失在你的脚下
另一件事是编译器/CLR优化。我将数据源分配给temp,并使用temp访问数据成员。CLR做什么?它是真的复制了dataSource还是优化器只是使用dataSource访问.a和.b
这取决于dataSource
和temp
是什么,取决于它们是否是本地的,以及
var a = dataSource.a;
... // many instructions
var b = dataSource.b;
/* Thread A */
dataSource = null;
/* Some time has passed */
/* Thread B */
var isNull = dataSource == null;