更新 Parallel.ForEach 线程中对象的属性是不是安全?
Posted
技术标签:
【中文标题】更新 Parallel.ForEach 线程中对象的属性是不是安全?【英文标题】:Is updating a property for an object in a Parallel.ForEach thread safe?更新 Parallel.ForEach 线程中对象的属性是否安全? 【发布时间】:2021-05-27 13:14:22 【问题描述】:Public Class Customer
public int CustomerIdget;set;
Public string CustomerNameget;set;
public class RunParallel(List<Customer> namelessCustomers)
Parallel.ForEach(namelessCustomers, (customer) =>
var customerName = CallAPIToReturnCustomerName(customer.CustomerId);
customer.CustomerName = customerName
您好,customer.CustomerName=customerName
线程安全。我想要的是更新客户列表,以便每个对象都获得客户名称。
如果不是,我怎样才能让这样的事情起作用。你能解释一下为什么这不是线程安全的吗?
【问题讨论】:
每个并行迭代似乎都在不同的Customer
实例上工作,那么为什么这不是线程安全的呢? (假设CallApiToReturnCustomerName
是线程安全的)
对不起,我不擅长多线程。
【参考方案1】:
首先,字符串是不可变的,而引用是原子的,所以这个属性是线程安全的,并不是说它不会过时价值观和数据竞争——尽管这里似乎并非如此
其次,您在parallel.ForEach
中调用IO 工作负载,这不是最佳的。这意味着,您正在阻塞和捆绑 线程池 线程,这些线程可以有效地卸载到 IO 完成端口。您最好让 API 调用为async
,并使用Task.WhenAll
示例
public async Task<Customer> CallAPIToReturnCustomerNameAsync(int customerId)
///await someThingAsyncHere();
...
async Task Process(Customer customer)
customer.CustomerName = await CallAPIToReturnCustomerNameAsync(customer.CustomerId);
var tasks = namelessCustomers.Select(Process);
await Task.WhenAll(tasks);
【讨论】:
【参考方案2】:主要的危险是并发读/写或写/写。如果保证每个对象只能从单个线程访问,则默认情况下这将是线程安全的,即该列表没有同一客户的多个副本,并且在循环运行时没有其他线程可以访问该客户列表。
即使有并发访问,只要类型是引用类型,或者值类型小于本机整数,这也是原子的,如本例中的情况。但是,这仅适用于单个读/写访问,任何涉及多个操作的操作都是不安全的。
【讨论】:
【参考方案3】:线程安全和并发是两种在两种情况下值得注意的方法: 1-并发读取器/写入器可以进入一个关键区域。 (即使在并发编程或并行性中)并发编程是指多线程,并行性是指多核编程。 2-作家引起读者,反之亦然。例如,在单个区域上写入之前,您必须阅读和检查。 上述两条规则都可能导致竞态条件,应该值得注意。 在你的情况下:
您没有任何种族,因为您只是在写作,顺序并不重要。 您不再有任何比赛,因为您在多个领域写作,并且领域(不同客户的财产)不共享。注意:竞态条件与多 X 编程的方式/堆栈/语言无关,您可能在多线程、多核编程中都有这种情况。因此,我强烈建议您仔细检查您的代码并找到关键区域并写入/写入或写入/读取时刻。你会发现没有:)
【讨论】:
以上是关于更新 Parallel.ForEach 线程中对象的属性是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章
何时使用 Parallel.ForEach,何时使用 PLINQ