从整体上看来,所有线程之间的共享数据的问题,都是修改数据导致的。如果把数据的读取者称为读者,修改者称为写者,那么在以下情况下可以不进行数据保护
- 数据只有读者没有写者时
- 数据只有一个写者且读者可以识别或不在意得到的信息是否完整的时候
- 数据的写者的写操作是原子操作
若不满足以上条件中的任意一条,则必须为写加锁。但并不是必须为读加锁,如果读者能够识别自己读到的信息是否是一条完整的信息,那么即使有多个写者,也可以只锁写不锁读。这样一定程度上能够降低锁粒度。
接下来来解释以上结论
1 数据只有读者没有写者
这种情况是相对最纯净的情况,数据在内存中保持不变,不论何时哪个CPU访问这段内存,得到的都是一样的完整正确的结果,因此不需要任何保护。
2 数据只有一个写者且读者可以识别或不在意得到的信息是否完整的时候
数据只有一个写者,因此只有一个线程会修改这个数据,这个情况就像运行一个控制台应用,cout的字符串会被写在控制台在内存中的缓冲区里,等待控制台窗口打印完上一个字符串后,再到缓冲区里面取下一条。这种状况下有可能发生的最坏的情况就是cout在向内存中写入数据的过程中被进程调度打断,比如X86中写宽字符,只写了底八位,而高八位还保持原始状态时被打断,这样控制台可能会读到一个错误的字符。
然而对于诸如控制台输出这样的任务而言,通常它的实现方式使得它能够识别自己是否得到了一个完整的字符,比如写者每写完一个字符会为缓冲区的顶部标记加一,而控制台发现自己已经输出到缓冲区标记,就会明白,下个字符不是还没有写入,就是还没有写完。
3 数据的写者的写操作都是原子操作
这一点和上一点的主要差别在于存在复数个写手,因此当一个写入的过程被打断,有可能另一个写入的过程会插在中间,这样就会留下乱序的数据。除非这个写入过程是原子的。
但进一步说,原子的写入操作是很少见的,如果是从高级语言编译的程序,很可能连一个简单的整形赋值操作都无法保证原子性,如果涉及到I/O,连单一的汇编语句也不是原子性的。所以这种情况的条件是十分苛刻的。
4 有多个写者,但读者可以识别或不在意得到的信息是否完整的时候
这种情况下必须为写者加锁,因为需要防止写的过程被其他写的过程打断,造成写入混乱。但在读者能识别自己读到的信息的完整性的情况下,只要确保写入的信息是顺序的,就不会造成读者方面的错误。因此此时不需要为读者加锁