List是线程安全的吗?如果不是该怎么办呢?安全的List对性能的影响有多大呢?
测试条件:
开启2个并行执行任务,往同一个list对象写入值
测试代码:
static int maxNum = 1000000; static List<int> list = new List<int>(); static void Main(string[] args) { //迭代次数 int iterationNum = 3; CodeTimer.Initialize(); CodeTimer.Time("List是否是线程安全的呢?", iterationNum, new Action(ListIsThreadSafe)); //Console.Write(sbIsThreadSafe.ToString()); Console.Read(); } private static void ListIsThreadSafe() { Parallel.For(1, maxNum / 2, (i) => { list.Add(i); }); Parallel.For(maxNum / 2 + 1, maxNum, (i) => { list.Add(i); }); }
测试结果:
测试结论:
之所以会造成以上的结果是因为list对象不是线程安全。那该怎么办呢?
这时我们需要使用System.Collections.Concurrent命名空间下的类型来用于并行循环体内。
类 |
说明 |
BlockingCollection<T> |
为实现 IProducerConsumerCollection<T> 的线程安全 集合提供阻止和限制功能。 |
ConcurrentBag<T> |
表示对象的线程安全的无序集合。 |
ConcurrentDictionary<TKey, TValue> |
表示可由多个线程同时访问的键值对的线程安全集合。 |
ConcurrentQueue<T> |
表示线程安全的先进先出 (FIFO) 集合。 |
ConcurrentStack<T> |
表示线程安全的后进先出 (LIFO) 集合。 |
OrderablePartitioner<TSource> |
表示将一个可排序数据源拆分成多个分区的特定方式。 |
Partitioner |
提供针对数组、列表和可枚举项的常见分区策略。 |
Partitioner<TSource> |
表示将一个数据源拆分成多个分区的特定方式。 |
我们再来进行一次测试。
测试代码:
static int maxNum = 1000000; static ConcurrentQueue<int> safeList = new ConcurrentQueue<int>(); static void Main(string[] args) { //迭代次数 int iterationNum = 3; CodeTimer.Initialize(); CodeTimer.Time("ConcurrentQueue是否是线程安全的呢?", iterationNum, new Action(ListIsThreadSafe)); //Console.Write(sbIsThreadSafe.ToString()); Console.Read(); } private static void ListIsThreadSafe() { Parallel.For(1, maxNum / 2, (i) => { safeList.Enqueue(i); }); Parallel.For(maxNum / 2 + 1, maxNum, (i) => { safeList.Enqueue(i); }); }
测试结果:
测试结论:
ConcurrentQueue是线程安全的。在遇到多线程和并行开发时应该使用ConcurrentQueue而不是List.
疑问:为了确保对象线程安全,系统底层实现机制必定是要使用对象加锁和解锁的功能来确保对象安全,那么加锁和解锁是否会对性能造成影响呢?
我们再来进行一组测试:
测试条件:
(1)对100万个数据进行普通的循环运算然后添加到List集合对象中。
(2)对100万个数据进行并行循环运算然后添加到ConcurrentQueue集合对象中。
测试代码:
static int maxNum = 1000000; static List<BigInteger> list = new List<BigInteger>(); static ConcurrentQueue<BigInteger> safeList = new ConcurrentQueue<BigInteger>(); static void Main(string[] args) { //迭代次数 int iterationNum = 3; CodeTimer.Initialize(); CodeTimer.Time("普通的循环运算", iterationNum, new Action(NormalCompute)); CodeTimer.Time("并行循环运算_1", iterationNum, new Action(ParallelCompute_1)); CodeTimer.Time("并行循环运算_2", iterationNum, new Action(ParallelCompute_2)); Console.Read(); } private static void NormalCompute() { for (int i = 1; i <= maxNum; i++) { Math.Pow(i, i + 1); list.Add(new BigInteger(i)); } } private static void ParallelCompute_1() { Parallel.For(1, maxNum, (i) => { Math.Pow(i, i + 1); safeList.Enqueue(new BigInteger(i)); }); } private static void ParallelCompute_2() { Parallel.For(1, maxNum / 2, (i) => { Math.Pow(i, i + 1); safeList.Enqueue(new BigInteger(i)); }); Parallel.For(maxNum / 2 + 1, maxNum, (i) => { Math.Pow(i, i + 1); safeList.Enqueue(new BigInteger(i)); }); }
测试结果:
测试结论:
和我预期的结论是一样的,并行方式所耗费的时间更加的长。线程安全的加锁和解锁对性能的影响还是比较大的。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。