
线程安全问题的来源
线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
可以看到这一切的诱因就是因为共享变量。如果我们的线程执行过程中,没有相互影响,就不会出现问题。
当我们的线程访问共享变量时,我们无法预测操作系统是否将为我们的线程选择一个正确的顺序。这就尴尬了!
对多个线程对资源的访问,我们称之为 竞争。
由于两个或者多个进程竞争使用不能被同时访问的资源,使得这些进程有可能因为时间上推进的先后原因而出现问题,这叫做竞争条件(Race Condition)。
竞争条件分为两类
- Mutex 不能被多个进程同时使用的资源
- Synchronization 两个或多个进程彼此指针存在内在的制约关系
- 消费者生产者问题就是同步问题,它需要调度对共享资源的访问,是 Synchronization。
- 读者写者问题是互斥问题的一个概括。
消费者生产者问题
因为插入和取出项目都涉及更新共享变量,所以我们必须保证对缓冲区的访问是互斥的。但是只保证互斥访问是不够的,我们还需要调度对缓冲区的访问。如果缓冲区是满的,那么生产者必须等待直到有一个槽位变为可用。与之相似,如果缓冲区是空的,那么消费者必须等待直到有一个项目变为可用。
读者-写者问题
读者写者问题是互斥问题的一个概括。一组并发的线程要访问一个共享对象。写者必须拥有对对象独占的访问。
如何保证线程安全
在我们需要访问共享变量的情况下,我们需要保证线程执行顺序的正确性。或者我们尽量避免使用共享变量。
进度图
如果我们用 纵横坐标分别表示两个进程执行的指令顺序,那么操作共享变量的指令会构成一个二维的不安全区,当两条线程的执行轨迹会同时访问不安全区时,我们就认为执行是不安全的。(局限性:无法描述多处理器并发执行)
我们可以选择安全的轨迹线,或者使用信号量实现互斥。
锁
思考一下,锁做了什么?
lock是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足.
对于竞争条件中的 Mutex 我们可以使用互斥锁处理。Synchronization 可以使用条件锁。
使用lock是可以保证线程安全的,但不能保证线程的执行顺序。
原子性
原子性(Atomic),一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。
OC 中一个很经典的面试题是, property 设置 atomic 能保证线程安全吗?
很多人都回答可以,这是压根没理解线程安全的体现。
原子性不能保证线程安全
原子性可以保证写操作一小块代码段是互斥的,但是并不能保证线程安全。
设置atomic之后,只是保证了 属性的赋值操作是互斥的,可惜只是该属性..
不能保证我们整个代码的线程安全。
考虑一段代码.
1 |
self.a = 0; |
当 self.a = 0; 执行完毕后
处理机调度,切换到另一个线程执行
1 |
self.b = 0; |
再次切换为原先的线程
此时,结果是
unsafe.
我们原先的值被其他线程篡改了。并不能保证线程安全。
会想一下 那经典的 进度图,原子性无法阻止多个线程访问不安全区。
加锁之后,就保证 代码块 不会被多个进程访问,保证了线程安全。
1 |
lock(); |
不可变性
不可变性,这个跟线程安全关系大吗?
显然,我们对共享变量的访问,会导致线程安全问题是因为我们对其进行了写操作。不可变可以避免代码编写中因为疏忽导致的问题。真正处理线程安全的时候,你遇到的,会是可变的共享变量!
所以,这个对线程安全问题,没有用处。




近期评论