Code Bye

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

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 呢?

解决方案

10

bag.Count >= 300 的判断和bag.Add两者不是原子操作。可能两个线程都判断bag.Count =299,然后各自插入一个,虽然读取、插入是线程安全的,但是不能保证结果是300

5

线程安全的意思就是多线程做某个操作结果和你预期的是一样的
List不是线程安全的,同时添加数据自然会出问题
假如你要刨根问底,为什么会出这个错,不妨看下List.Capcity的代码

10

线程安全跟加不加lock 没有关系。
所谓线程安全,是说它的任何在单线程上安全的操作在瞬间并发时也不会抛出逻辑出错。例如一个线程读取 list[2] 单元,另一个线程也读取 list[2] 单元,在逻辑上不会抛出异常!
只要一个操作在瞬间并发时并不会出现逻辑错误,这就是线程安全的。再例如说一个线程执行 list.Move(n),另一个线程也执行 list.Move(n),或许结果是对的(例如删除了某个对象)也可能是错误(例如把紧挨在一起的两个对象都删除了),但是不会报错,这就说明它是安全的。
例如说一个线程为 list 增加了一个单元,并不会影响另外一个线程读取 list.Count,这就是说明它是线程安全的。
线程安全的,并不代表着“先后次序的不同操作”不需要考虑逻辑出错问题。线程安全的东西,实际上一点也没有保证数据不会混乱!你在设计前后流程逻辑时照样要使用lock等等同步考虑,你不知道线程安全是在哪一个具体的点上“是安全的”,怎么就能随便“抠字眼儿”就说线程安全就不用lock了呢?
线程安全的意思,是说技术数据混乱了,程序仍然不报错。这就好例如说你使用一个 NoSql 而可以随便并发修改数据(甚至混乱地修改数据),儿你并不会像传统的关系数据库一样收到“事务冲突、事务加锁超时”之类的异常。
实际上,线程安全的,就意味着它很容易数据混乱,而并不会报错!所以仍然需要你亲自编代码用 lock 等语句来保证业务意义上的数据一致性。

10

例如说一个国家它说“随便什么人都可以越境进入,根本不会原因是偷渡而被捕”,于是你就说这个国家是“安全的”。问一下这个国家的治安是安全的吗?
你就是误会了“线程安全的”这个概念。线程安全的,并不意味着你要少一点点对 lock 等等数据安全的考虑。
有些类不但是“线程安全的”,而且花费了巨大的时间和空间代码来做到了“私有化”,你认为的“安全”是那种东西低效率的安全吧?!

5

很高兴能解答这个问题。其实您只要记住一句话就好:线程安全和数据同步是两码事儿。
您的程序是线程安全的,不会出错(起码计算机是这么认为的)
但他的数据是不同步的,原因是你是并发运行,速度太快,计算机也不确定谁先谁后。
所以你需要同步一下。就是LOCK起来。C#有很多线程同步的类 什么信号量互斥量 锁 自旋锁。
看你需要选择合适的用。

CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明C#的线程安全类为何还是得加 lock