C#的线程安全类为何还是得加 lock ?

吉普赛的歌 2016-07-10 09:54:44
public void ParallelBreak()
{
Console.WriteLine("\n————— {0} —————", MethodBase.GetCurrentMethod().Name);
ConcurrentBag<int> bag = new ConcurrentBag<int>();
stopWatch.Start();
Parallel.For(0, 1000, (i, state) =>
{
//此处加 lock (bag) 则输出一定是 300. 否则不一定是 300 , 可能是 302, 300, 306 等
if (bag.Count >= 300)
{
state.Stop();
return;
}
bag.Add(i);
});
stopWatch.Stop();
Console.WriteLine("Bag count is " + bag.Count + ", " + stopWatch.ElapsedMilliseconds);
}

以上代码, 输出为:


不太明白, 既然是 线程安全类, 为什么不加 lock 就不能保证输出为 300 呢?
...全文
750 1 收藏 9
写回复
9 条回复
kaikai1999 2018年04月17日
一楼OK,后面的回答会误导人的。
回复 点赞
ming_1976 2017年11月16日
引用 6 楼 DOwnstairs 的回复:
很高兴能解答这个问题。其实您只要记住一句话就好:线程安全和数据同步是两码事儿。 您的程序是线程安全的,不会出错(起码计算机是这么认为的) 但他的数据是不同步的,因为你是并发运行,速度太快,计算机也不确定谁先谁后。 所以你需要同步一下。就是LOCK起来。。C#有很多线程同步的类 什么信号量互斥量 锁 自旋锁。 看你需要选择合适的用。
一语惊人,明白了,lock是为了同步用的。
回复 点赞
chenzhe014 2017年07月06日
我的理解是 ConcurrentBag.Count<T>() 是ConcurrentBag的扩展方法,由 Enumerable<T>定义。所以其并不是线程安全,所以需要加锁(ConcurrentBag.Add()是ConcurrentBag<T> 类的方法,所以是线程安全)。
回复 点赞
SoulRed 2016年07月11日
很高兴能解答这个问题。其实您只要记住一句话就好:线程安全和数据同步是两码事儿。 您的程序是线程安全的,不会出错(起码计算机是这么认为的) 但他的数据是不同步的,因为你是并发运行,速度太快,计算机也不确定谁先谁后。 所以你需要同步一下。就是LOCK起来。。C#有很多线程同步的类 什么信号量互斥量 锁 自旋锁。 看你需要选择合适的用。
回复 点赞
以专业开发人员为伍 2016年07月10日
比如说一个国家它说“随便什么人都可以越境进入,根本不会因为偷渡而被捕”,于是你就说这个国家是“安全的”。请问这个国家的治安是安全的吗? 你就是误会了“线程安全的”这个概念。线程安全的,并不意味着你要少一点点对 lock 等等数据安全的考虑。 有些类不但是“线程安全的”,而且花费了巨大的时间和空间代码来做到了“私有化”,你认为的“安全”是那种东西低效率的安全吧?!
回复 点赞
以专业开发人员为伍 2016年07月10日
线程安全跟加不加lock 没有关系。 所谓线程安全,是说它的任何在单线程上安全的操作在瞬间并发时也不会抛出逻辑出错。例如一个线程读取 list[2] 单元,另一个线程也读取 list[2] 单元,在逻辑上不会抛出异常! 只要一个操作在瞬间并发时并不会出现逻辑错误,这就是线程安全的。再比如说一个线程执行 list.Move(n),另一个线程也执行 list.Move(n),或许结果是对的(例如删除了某个对象)也可能是错误(例如把紧挨在一起的两个对象都删除了),但是不会报错,这就说明它是安全的。 比如说一个线程为 list 增加了一个单元,并不会影响另外一个线程读取 list.Count,这就是说明它是线程安全的。 线程安全的,并不代表着“先后次序的不同操作”不需要考虑逻辑出错问题。线程安全的东西,实际上一点也没有保证数据不会混乱!!!你在设计前后流程逻辑时照样要使用lock等等同步考虑,你不知道线程安全是在哪一个具体的点上“是安全的”,怎么就能随便“抠字眼儿”就说线程安全就不用lock了呢? 线程安全的意思,是说技术数据混乱了,程序仍然不报错。这就好比如说你使用一个 NoSql 而可以随便并发修改数据(甚至混乱地修改数据),儿你并不会像传统的关系数据库一样收到“事务冲突、事务加锁超时”之类的异常。 实际上,线程安全的,就意味着它很容易数据混乱,而并不会报错!所以仍然需要你亲自编代码用 lock 等语句来保证业务意义上的数据一致性。
回复 点赞
Poopaye 2016年07月10日
线程安全的意思就是多线程做某个操作结果和你预期的是一样的 List不是线程安全的,同时添加数据自然会出问题 如果你要刨根问底,为什么会出这个错,不妨看下List.Capcity的代码
回复 点赞
吉普赛的歌 2016年07月10日
引用 1 楼 caozhy 的回复:
bag.Count >= 300 的判断和bag.Add两者不是原子操作。可能两个线程都判断bag.Count =299,然后各自插入一个,虽然读取、插入是线程安全的,但是不能保证结果是300


多谢版主, 还提一个问题:
下面的这段代码, 大多数情况是可以运行的, 但有时又会报下图的错误, 是什么原因呢?
public static void ListWithParallel()
{
Console.WriteLine("————— {0} —————", MethodBase.GetCurrentMethod().Name);
List<int> list = new List<int>();
Parallel.For(0, 10000, item =>
{
list.Add(item);
});
Console.WriteLine("List's count is {0}", list.Count());
}

回复 点赞
bag.Count >= 300 的判断和bag.Add两者不是原子操作。可能两个线程都判断bag.Count =299,然后各自插入一个,虽然读取、插入是线程安全的,但是不能保证结果是300
回复 点赞
发动态
发帖子
C#
创建于2007-09-28

8.5w+

社区成员

64.0w+

社区内容

.NET技术 C#
社区公告
暂无公告