并发遍历实现线程安全遍历

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发遍历实现线程安全遍历相关的知识,希望对你有一定的参考价值。

参考技术A 并发集合实现线程安全的遍历通常有两种方式:

一是对遍历对象的快照进行遍历。二是对遍历对象进行准实时的遍历。

快照是在 Iterator 实例被创建的那一刻待遍历对象内部结构的一个吟诗副本(对象),它反映了待遍历集合的某一时刻(即 Iterator 实例被创建的那一刻)的状态(不包括集合元素的状态)。由于对同一个并发集合进行遍历操作的每个线程会得到各自的一份快照,因此快照相当于这些线程的线程特有对象。所以,这种方式下进行遍历操作的线程无须加锁就可以实现线程安全。另一方面,快照是只读的,这种 Iterator 实例是不支持 remove 方法的。这种方式的优点是遍历操作和更新操作之间互不影响;缺点是当被遍历的集合比较大时,创建快照的直接或者是间接开销会比较大。 CopyOnWriteArrayList 和 CopyOnWriteArraySet 就是使用这种遍历方法。

准实时是指遍历操作不是针对待遍历对象的副本进行的,但又不借助锁来保障线程安全,从而使得遍历操作可以与更新操作并发进行。并且,遍历过程中其他线程对被遍历对象的内部结构的更新(比如删除元素)可能会(也可能不会)被反映出来。这种遍历方式所返回的 Iterator 实例可以支持 remove 方法。 ConcurrentLinkedQueue 和 ConcurrentHashMap 等并发集合就采用这种遍历方式。由于 Iterator 是被设计用来一次只被一个线程使用的,因此如果有多个线程需要进行遍历操作,那么这些线程之间是不适宜共享同一个 Iterator 实例的。

C# 在多线程环境中,进行安全遍历操作

本文以List作为操作对象
MSDN官方给出的List的线程安全的说法:
此类型的公共静态成员是线程安全的。但不能保证任何实例成员是线程安全的。
只要不修改该集合,List 就可以同时支持多个阅读器。通过集合枚举在本质上不是一个线程安全的过程。在枚举与一个或多个写访问竞争的罕见情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。
如果不进行同步操作?
假如一个线程进行删除操作,一个线程进行遍历操作,那么在遍历过程中,集合被修改,会导致出现InvalidOperationException的异常,提示:集合已修改;可能无法执行枚举操作。
如何同步,保证遍历的安全
这里使用了临界区,互斥锁来保证线程遍历过程的安全,示例代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Threading;
 4 
 5 namespace ConsoleApp
 6 {
 7     class Program
 8     {
 9         public static List<string> simpleList = new List<string>();
10         
11         public static void Main(string[] args)
12         {
13             // 临界区对象
14             object lockObj = new object();
15 
16             // 向List中添加测试数据
17             string[] data = { "1", "2", "3", "4", "5", "6", "7", "8" };
18             simpleList.AddRange(data);
19             // 此线程用于遍历数组
20             new Thread(new ThreadStart(() =>
21             {
22                 // 用于同步,进入临界区,只有遍历完,释放临界区对象的互斥锁,才能进行写操作
23                 lock (lockObj)
24                 {
25                     foreach (var item in simpleList)
26                     {
27                         Console.WriteLine(item);
28                         Thread.Sleep(500);
29                     }
30                 }
31             })).Start();
32             // 此线程执行删除操作
33             new Thread(new ThreadStart(() =>
34             {
35                 lock (lockObj)
36                 {
37                     simpleList.RemoveAt(0);
38                     Console.WriteLine("rm 1");
39                     Thread.Sleep(500);
40                     simpleList.RemoveAt(0);
41                     Console.WriteLine("rm 2");
42                 }
43             })).Start();
44 
45             Console.ReadLine();
46         }
47     }
48 }

 

以上是关于并发遍历实现线程安全遍历的主要内容,如果未能解决你的问题,请参考以下文章

C#15条线程遍历同一个DataTable,在线等

如何线程安全地遍历List:VectorCopyOnWriteArrayList

怎样线程安全地遍历List:VectorCopyOnWriteArrayList

Java核心技术 卷1 多线程----线程安全的集合

多线程使用for循环遍历同一个Set,是否线程安全?

List 集合线程安全测试