【问题标题】:Is it thread safe to update different properties of an object from different threads?从不同线程更新对象的不同属性是否线程安全?
【发布时间】:2018-08-27 14:59:48
【问题描述】:

考虑下面的伪代码。我有一个具有 3 个属性的类,每个属性都从不同的方法并行填充。

从不同的线程填充同一个类实例的不同属性是否会遇到麻烦?我设置了一个.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();
}

【问题讨论】:

  • 你期待什么麻烦?
  • @trailmax 线程死锁,线程安全 - 那种性质的东西。
  • 任务和线程不是一回事。当您使用 Task 时,它更像是对未来结果的承诺。所以用这些术语思考
  • @FortyTwo 在这种情况下,Task.Run 与 Thread.Start 相同——在功能上它们做同样的事情。

标签: c# .net multithreading


【解决方案1】:

因为只有一个类的实例(保存在response 局部变量中),所以在类级别没有什么可以锁定的。 response 变量在传递到这些任务时有一个隐式捕获的闭包,但这只是一个指针,指向位于堆内存中的实例,而不是对象本身。

由于每个自动属性的支持字段是独立的(保存在类实例中的单独内存位置中),并且每个方法都更新单独的属性(因此是单独的字段),因此您没有更新冲突。这些支持字段不是相互依赖的。

换句话说,没有两个任务试图同时更新同一个内存位置,因此这段代码是线程安全的。

然而,如果您有多个任务同时更新相同项,那么它不是线程安全的,并且您必须实现锁定机制或将数据存储在固有的线程安全对象中(例如ConcurrentDictionary 等)。

【讨论】:

  • 这对属性来说是松散的;您无法知道两个属性的调用图是否最终触及相同的内存。例如,更改由单个位字段支持的属性是不安全的。即使使用引用类型,您也可能会遇到意外。
  • 这个例子中只有一个类的实例。但是,它在 Web 服务器中运行,因此会有数千个实例。还安全吗?
  • @zneak - 正确。但在这种特殊情况下,每个自动实现的属性都有自己的字段支持,因此没有冲突。我不提倡使用这样的代码 - 只是说 此处显示的代码 不需要锁定。
  • 当然。请注意,问题的标题不够具体,并且将来可能会脱离上下文找到您的答案。
  • @AngryHacker - 如果每个更新任务只更新一个实例,那么就没有冲突。如果您有两个任务可能会在同一个实例上遇到相同的属性,那么就有。
【解决方案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

【讨论】:

    猜你喜欢
    • 2023-03-23
    • 2021-05-27
    • 1970-01-01
    • 2018-03-12
    • 2015-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-16
    相关资源
    最近更新 更多