勇哥注:
《多线程安全》这个系列会持续写下去,它是我的一个弱点,有兴趣的朋友可以选择性看看。
程序如下:
private void button1_Click(object sender, EventArgs e) { var list1 = new List<int>(); for(int i=0;i<10000;i++) { Task.Run(() => { list1.Add(i); }); } Thread.Sleep(6000); Console.WriteLine(list1.Count); }
程序的目的是: 循环10000次,往集合中添加值。
由于循环中使用task往集合中添加值 ,因此发生了问题。
输出结果9960表示写集合丢失了数据。
如果你多次执行,会发现这个数量还是变化的。
因为list类似数组,是一片连续内存区,靠索引添加数据。
多线程时,可能两个线程同一时间操作了同一个内存区
并且上篇所讲的原因,这个list1中保存的数据是不是从0到9999,而是一片片相同数据构成。
如果我们去掉task.run,则代码结果是正常的,数量为10000。这个时候相当于是单线程执行。
从这个例子引入了 线程安全的定义:
一段代码,如果单线程与多线程执行结果不同,则表示它存在线程安全问题。
解决办法有:
1是加lcok,2是使用线程安全集合
加lock锁的结果如下,是正确的。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { var list1 = new List<int>(); for(int i=0;i<10000;i++) { Task.Run(() => { lock (lockobj) { list1.Add(i); } }); } Thread.Sleep(6000); Console.WriteLine(list1.Count); } private static readonly object lockobj = new object(); } }
其中 private static readonly object的写法是微软推荐的写法,是有深意的。
后面会谈到。
另一个知识点是,lock关键字是Monitor类的封装。
下面的代码效果跟lock是一样的。
private void button3_Click(object sender, EventArgs e) { var list1 = new List<int>(); for (int i = 0; i < 10000; i++) { Task.Run(() => { Monitor.Enter(lockobj); { list1.Add(i); } Monitor.Exit(lockobj); }); } Thread.Sleep(6000); Console.WriteLine(list1.Count); }
使用线程安全集合的例子如下:
你会看到结果也是正确的。
private void button2_Click(object sender, EventArgs e) { BlockingCollection<int> list1 = new BlockingCollection<int>(); for (int i = 0; i < 10000; i++) { Task.Run(() => { list1.Add(i); } ); } Thread.Sleep(6000); Console.WriteLine(list1.Count); }
有关线程安全集合的详细介绍见贴子
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!

