不同线程更新对象的不同属性是否线程安全?

4
考虑下面的伪代码。我有一个类,其中包含3个属性,每个属性都从不同的方法并行填充。将同一类实例的不同属性从单独的线程中填充是否会导致问题?我已经设置了一个{{link1:.net fiddle}},看起来它运行得很好。如果这段代码会引起线程问题,我应该使用哪种方法锁定Response类的特定实例,同时填充属性?
class Response {
    public string Response1 { get; set; }
    public string Response2 { get; set; }
    public string Response3 { get; set; }
}

void foo() {
    var response = new Response();

    var task1 = Task.Run(() => GetResponse1(response));
    var task2 = Task.Run(() => GetResponse2(response));
    var task3 = Task.Run(() => GetResponse3(response));

    Task.WaitAll(task1, task2, task3);        
}

void GetResponse1(Response response) {
    response.Response1 = fetchSomeValue1();
}

void GetResponse2(Response response) {
    response.Response2 = fetchSomeValue2();
}

void GetResponse3(Response response) {
    response.Response3 = fetchSomeValue3();
}

1
你预计会遇到什么问题? - trailmax
@trailmax 线程死锁、线程安全等相关问题。 - AngryHacker
任务和线程不是同一回事。当你使用任务时,它更像是未来结果的承诺。因此,请从这些角度考虑。 - FortyTwo
@FortyTwo 在这种情况下,Task.Run 和 Thread.Start 是相同的 - 从功能上讲,它们做相同的事情。 - AngryHacker
2个回答

4
由于该类只有一个实例(存储在response本地变量中),因此在类级别上没有任何需要锁定的内容。当response变量传入这些任务时,会隐式捕获闭包,但它只是指向堆内存中的实例的指针,而不是对象本身。
由于每个自动属性的后备字段是独立的(存储在类实例的不同内存位置中),并且每个方法都更新一个单独的属性(因此是单独的字段),所以没有更新冲突。这些后备字段不相互依赖。
换句话说,没有两个任务尝试同时更新同一内存位置,因此这段代码是线程安全的。 然而,如果有多个任务可能同时更新相同的项目,则它将是线程安全的,您必须实现锁定机制或将数据存储在固有线程安全的对象中(例如ConcurrentDictionary等)。

2
这对于属性来说是一种不严谨的做法;你无法知道两个属性的调用图最终是否会触及相同的内存。例如,更改由单个位字段支持的属性将是不安全的。即使使用引用类型,你也可能会遇到意外情况。 - zneak
在这个例子中,类只有一个实例。然而,它在 Web 服务器上运行,因此会有成千上万个实例。仍然安全吗? - AngryHacker
1
当然。请注意问题标题缺乏明确性,以后您的答案可能会被断章取义。 - zneak
1
@AngryHacker - 如果每个更新任务只更新单个实例,则没有冲突。如果有两个任务可能会同时命中同一实例上的同一属性,则存在冲突。 - Matt Johnson-Pint
@AngryHacker - 如果这代表了你的Web服务器中的代码,我强烈建议不要使用Task.WaitAll。相反,使用asyncawait,以及await Task.WhenAll(如果您有多个并行等待的任务)。如果您有I/O绑定任务(文件访问、网络访问、数据库访问等),请避免使用Task.Run。而是在异步API上使用,并通过您喜欢的任何排队机制将长时间运行或CPU密集型任务移动到单独的离线进程中。 - Matt Johnson-Pint
显示剩余8条评论

2

我建议你这样做:

var response = Task<Response>
                    .Factory
                    .ContinueWhenAll(new Task<string>[]{task1, task2, task3},
                                            tasks => new Response {
                                                                    Response1 = tasks[0].Result, 
                                                                    Response2 = tasks[1].Result, 
                                                                    Response3 = tasks[2].Result
                                                                    })
                    .Result;

https://dotnetfiddle.net/LHUa6G


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接