# Bison眼中的iOS开发多线程是这样的（二）

语言：zh-CN
原文：[Bison眼中的iOS开发多线程是这样的（二）](<https://allluckly.cn/%E5%A4%9A%E7%BA%BF%E7%A8%8B/duoxiancheng02>)
中文：[中文 HTML](<https://allluckly.cn/%E5%A4%9A%E7%BA%BF%E7%A8%8B/duoxiancheng02>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-01-18-bison-s-guide-to-ios-multithreading-part-2/>)
发布于: 2016-01-18T00:00:00+08:00
内容更新于: 2016-01-18T00:00:00+08:00
署名：[Bison](<https://allluckly.cn/about/>)

围绕共享数据被多个线程访问的情形，介绍 synchronized 同步代码块和 NSLock 的加解锁方式，再用 NSCondition 的等待、唤醒与广播解释线程之间的协调。

原文署名 Bison，保留原发布时的内容。

**阅读前** 这是 2015–2017 年间的技术记录，本次整理未重新运行验证示例。涉及的 API、依赖和工具版本请结合发表日期阅读。

> "多线程很容易突然出现“错误情况”，这是由于系统的线程调度具有一定的随机性造成的。不过，即使程序偶然出现问题，那也是由于编程不当所引起的。当使用多个线程来访问同一个数据时，很容易“偶然”出现线程安全问题。"

前面在《[Bison眼中的iOS开发多线程是这样的（一）](<https://allluckly.cn/%E5%A4%9A%E7%BA%BF%E7%A8%8B/duoxiancheng01>)》一文中讲到多线程的优先级，接下来我什么讲讲线程同步与线程通信  
 多线程很容易突然出现“错误情况”，这是由于系统的线程调度具有一定的随机性造成的。不过，即使程序偶然出现问题，  
 那也是由于编程不当所引起的。当使用多个线程来访问同一个数据时，很容易“偶然”出现线程安全问题。

关于线程安全问题，有OC的多线程支持引入了同步，使用同步的通用方法就是`@synchronized`修饰代码块，  
 被`@synchronized`修饰的代码块可简称为同步代码块。同步代码块的语法格式如下：

```css
@synchronized(obj){
    ...
    //此处的代码即为同步代码块
}
```

上面语法格式中`@synchronized`后面括号里的`obj`就是同步监视器。上面代码的含义是：  
 线程开始执行同步代码块之前，必须先获得对同步监视器的锁定。值得注意的是，人和时刻只能有  
 一个线程可以获得对同步监视器的锁定，当同步代码块执行完后，该线程会释放对同步监视器的锁定  
 虽然OC允许使用任何对象作为同步监视器，但想一下同步监视器的目的：阻止俩个线程对同一共享资  
 源进行并发访问，因此通常推荐使用可能被并发访问的共享资源充当同步监视器

线程安全的类具有如下特征：

> 该类的对象可以被多个线程安全的访问。

> 每个线程调用该对象的任意方法之后都将得到正确结果。

> 每个线程调用该对象的任意方法之后，该对象依然保持合理状态。

`Foundation`框架中很多类都是有可变和不可变俩种版本，其中不可变类总是线程安全的，因为它的  
 对象状态不可改变。而可变类的对象需要额外的方法来保证其线程安全。  
 将多个线程并发修改共享资源的临界区使用`@synchronized`修饰，这样即可保证任意时刻，最多只  
 能有一个线程进入临界区修改共享数据，从而就可以实现线程安全的类。  
 可变类的线程安全是以降低程序的运行效率作为代价的。为了减少线程安全所带来的负面影响，程序可  
 以采用如下策略：

> 不要对线程安全类的所有方法都进行同步，只对那些会改变竞争资源（共享资源）对方法进行同步。

> 如果可变类有俩种运行环境：单线程环境和多线程环境，则应该为该可变类提供俩种版本，线程不安全版本和安全版本。在单线程环境中使用线程不安全版本已保证性能，在多线程环境中使用线程安全版本。

任何线程在进入同步代码块之前，必须先获得对同步监视器的锁定，那么何时会释放对同步监视器的锁定呢？程序无法显示释放对同步监视器的锁定，线程会再如下几种情况下释放对同步监视器的锁定。

> 当线程的同步代码块执行结束，当前线程即将释放同步监视器。

> 当线程的同步代码块中遇到 goto、 return终止了该代码块、该方法的继续执行时，当前线程将会释放同步监视器。

> 当线程的同步代码块中出现了错误，导致该代码块异常结束时，将会释放同步监视器。典型的例子有:当程序调用`NSThread`的`sleepXxx`方法暂停线程时，线程不会释放同步监视器。

`Foundation`还提供了`NSLock`，通过显示定义同步锁对象来实现同步，在这种机制下，同步锁使用`NSLock`对象充当。

`NSLock`是控制多个线程对共享资源进行访问的工具。通常锁提供了对共享资源的独占访问，每次只能有一个线程对`NSLock`对象加锁，线程开始访问共享资源之前应县获得`NSLock`对象。

在实现线程安全的控制中，使用该`NSLock`对象可以显式的加锁、释放锁。下面我们来举个简单的🌰

```
NSLock* lock;
- (id)init
{
    self = [super init];
    if (self) {
        lock = [[NSLock alloc] init];
    }
    return self;
}

//定义需要保证线程安全的方法
- (void)Safety{
    
    [lock lock];

    //需要保证线程安全的方法
    
    [lock unlock];
}

```

当线程在系统内运行，线程的调度具有一定的透明度，程序通常无法准确控制线程的轮换执行，但我们可以通过一些机制来保证线程协调运行，也就是处理线程之间的通信。

对此`Foundation`提供了`NSCondition`类来处理多线程之间的通信，`NSCondition`实现了`NSLock`协议，因此也可以调用`lock`、`unlock`来实现线程同步。除此之外，`NSCondition`可以让那些已经锁定`NSCondition`对象却无法继续执行的线程释放`NSCondition`对象，`NSCondition`对象也可以唤醒其他处于等待状态的线程。

`NSCondition`提供了如下几个方法

```
//改方法导致当前线程一直等待，直到其他线程调用该NSCondition的signal方法或者broadcast方法 
- (void)wait;
//用于控制等待到指定时间点，如果到了该时间点，该线程将会被自动唤醒
- (BOOL)waitUntilDate:(NSDate *)limit;
//唤醒等待的单个线程，如果有多个，则随机唤醒一个。只有当前线程放弃对该NSCondition对象的锁定后（wait方法），才可执行
- (void)signal;
//唤醒等待的所有线程
- (void)broadcast;

```

今天暂时写到这！  
 预告：（三）主讲GCD多线程！

---

> 原文地址：[https://allluckly.cn/](<https://allluckly.cn/>)

好文推荐：[详解持久化Core Data框架的原理以及使用](<https://allluckly.cn/%E6%8C%81%E4%B9%85%E5%8C%96/chijiuhua01>)

版权归©Bison所有 如需转载请保留原文超链接地址！否则后果自负！

