iOS 多线程(五) - 线程同步

应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两个线程同时修改同一资源有可能以意想不到的方式互相干扰。

一、同步工具

1.内存屏障和挥发变量
(1)内存屏障

为了达到最佳性能,编译器通常会讲汇编级别的指令进行重新排序,尽可能保持 处理器的指令流水线。作为优化的一部分,编译器可能会对内存访问的指令进行重新排序(在它认为不会影响数据的正确性的前提下),然而,这并不一定都是正确的,顺序的变化可能导致一些变量的值得到不正确的结果。
内存屏障(memory barrier)是一个使用来确保内存操作按照正确的顺序工作的 非阻塞的同步工具。迫使处理器来完成位于障碍前面 的任何加载和存储操作,才允许它执行位于屏障之后的加载和存储操作。
只要在代码里面需要的地方简单的调用OSMemoryBarrier函数。

(2)挥发变量

挥发变量(Volatile Variables)是另外一种针对变量的同步工具。众所周知,CPU访问寄存器的速度比访问内存速度快很多,因此,CPU有时候会将一些变量放置到寄存器中,而不是每次都从内存中读取(例如for循环中的i值)从而优化代码,但是可能会导致错误。
例如,一个线程在CPU A中被处理,CPU A从内存获取变量F的值,此时,并没有其他CPU用到变量F,所以CPU A将变量F存到寄存器中,方便下次使用,此时,另一个线程在CPU B中被处理,CPU B从内存中获取变量F的值,改变该值后,更新内存中的F值。但是,由于CPU A每次都只会从寄存器中取F的值,而不会再次从内存中取,所以,CPU A处理后的结果就是不正确的。
对一个变量加上Volatile关键字可以迫使编译器每次都重新从内存中加载该变量,而不会从寄存器中加载。当一个变量的值可能随时会被一个外部源改变时,应该将该变量声明为Volatile。

2.锁

锁是最常用的同步工具。可以是使用锁来保护临界区(critical section),这些代码段在同一个时间只能允许被一个线程访问。

3.条件

条件是信号量的另外一个形式,它允许在条件为真的时候线程间互相发送信号。 条件通常被使用来说明资源可用性,或用来确保任务以特定的顺序执行。当一个线程 测试一个条件时,它会被阻塞直到条件为真。它会一直阻塞直到其他线程显式的修改信号量的状态。条件和互斥锁(mutex lock)的区别在于多个线程被允许同时访问一个条件。条件更多是允许不同线程根据一些指定的标准通过的守门人。

4.执行Selector例程

Cocoa 程序包含了一个在一个线程以同步的方式传递消息的方便方法。NSObject 类声明方法来在应用的一个活动线程上面执行selector的方法。这些方法允许你的 线程以异步的方式来传递消息,以确保它们在同一个线程上面执行是同步的。比如, 你可以通过执行selector消息来把一个从你分布计算的结果传递给你的应用的主线 程或其他目标线程。每个执行selector的请求都会被放入一个目标线程的 run loop 的队列里面,然后请求会按照它们到达的顺序被目标线程有序的处理。

二、同步成本和性能

同步帮助确保你代码的正确性,但同时将会牺牲部分性能。甚至在无争议的情况 下,同步工具的使用将在后面介绍。锁和原子操作通常包含了内存屏障和内核级别同 步的使用来确保代码正确被保护。如果,发生锁的争夺,你的线程有可能进入阻塞, 在体验上会产生更大的迟延。

三、锁使用

1.互斥锁

加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定代码,新线程就会进入休眠。

(1)@synchronized
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic,assign)NSInteger tickets;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tickets = 10;
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    
}
-(void)saleTickets{
    while (1) {
        @synchronized (self) {//()中添加一个OC对象,一般使用self
            [NSThread sleepForTimeInterval:1];
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"剩余 = %ld,%@",self.tickets,[NSThread currentThread]);
            }else{
                NSLog(@"售完,%@",[NSThread currentThread]);
                break;
            }
        }
    }
}
@end
//输出结果
2021-06-03 21:25:31.562140+0800 DJTestDemo[1248:34046] 剩余 = 9,{number = 7, name = (null)}
2021-06-03 21:25:32.565304+0800 DJTestDemo[1248:34046] 剩余 = 8,{number = 7, name = (null)}
2021-06-03 21:25:33.565538+0800 DJTestDemo[1248:34046] 剩余 = 7,{number = 7, name = (null)}
2021-06-03 21:25:34.567206+0800 DJTestDemo[1248:34046] 剩余 = 6,{number = 7, name = (null)}
2021-06-03 21:25:35.571493+0800 DJTestDemo[1248:34046] 剩余 = 5,{number = 7, name = (null)}
2021-06-03 21:25:36.575782+0800 DJTestDemo[1248:34042] 剩余 = 4,{number = 3, name = (null)}
2021-06-03 21:25:37.579654+0800 DJTestDemo[1248:34042] 剩余 = 3,{number = 3, name = (null)}
2021-06-03 21:25:38.580766+0800 DJTestDemo[1248:34042] 剩余 = 2,{number = 3, name = (null)}
2021-06-03 21:25:39.581603+0800 DJTestDemo[1248:34042] 剩余 = 1,{number = 3, name = (null)}
2021-06-03 21:25:40.582047+0800 DJTestDemo[1248:34042] 剩余 = 0,{number = 3, name = (null)}
2021-06-03 21:25:41.582832+0800 DJTestDemo[1248:34042] 售完,{number = 3, name = (null)}
2021-06-03 21:25:42.583213+0800 DJTestDemo[1248:34046] 售完,{number = 7, name = (null)}
  • 加锁的代码尽量少。
  • 添加的OC对象必须在多个线程中都是同一个对象。
  • 判断的时候OC对象要存在,如果代码中只有一个地方需要加锁,大多使用self,这个可以避免单独创建一个OC对象。
  • 优点是不需要显式的创建锁对象。
  • @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。如果不想隐式的异常处理例程带来的额外开销,可以使用锁对象。
(2)NSLock

所有锁(包括NSLock)的接口都是通过NSLocking协议定义的。它定义了lockunlock方法,使用这些方法获取锁和释放锁。
NSLock类还增加了tryLocklockBeforeData方法。
tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反它只是返回NO。
lockBeforeData:方法是获取一个锁,但是如果锁没有在规定时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic,assign)NSInteger tickets;
@property(nonatomic,strong)NSLock *testLock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tickets = 5;
    self.testLock = [[NSLock alloc]init];
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    
}
-(void)saleTickets{
    while (1) {
        [self.testLock lock];
        [NSThread sleepForTimeInterval:1];
        if (self.tickets > 0) {
            self.tickets--;
            NSLog(@"剩余 = %ld,%@",self.tickets,[NSThread currentThread]);
        }else{
            NSLog(@"售完,%@",[NSThread currentThread]);
            break;
        }
        [self.testLock unlock];
    }
}
@end
//输出结果
2021-06-03 21:48:14.364458+0800 DJTestDemo[1375:47726] 剩余 = 4,{number = 6, name = (null)}
2021-06-03 21:48:15.367638+0800 DJTestDemo[1375:47726] 剩余 = 3,{number = 6, name = (null)}
2021-06-03 21:48:16.371922+0800 DJTestDemo[1375:47726] 剩余 = 2,{number = 6, name = (null)}
2021-06-03 21:48:17.377570+0800 DJTestDemo[1375:47726] 剩余 = 1,{number = 6, name = (null)}
2021-06-03 21:48:18.378622+0800 DJTestDemo[1375:47726] 剩余 = 0,{number = 6, name = (null)}
2021-06-03 21:48:19.382038+0800 DJTestDemo[1375:47726] 售完,{number = 6, name = (null)}
(3)pthread_mutex
#import "ViewController.h"
#import //需要导入pthread
@interface ViewController ()
@property(nonatomic,assign)NSInteger tickets;
@end
pthread_mutex_t mutex;
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tickets = 5;
    pthread_mutex_init(&mutex, NULL);
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    
}
-(void)saleTickets{
    while (1) {
        //加锁
        pthread_mutex_lock(&mutex);
        [NSThread sleepForTimeInterval:1];
        if (self.tickets > 0) {
            self.tickets--;
            NSLog(@"剩余 = %ld,%@",self.tickets,[NSThread currentThread]);
        }else{
            NSLog(@"售完,%@",[NSThread currentThread]);
            pthread_mutex_destroy(&mutex);//释放锁
            break;
        }
        //解锁
        pthread_mutex_unlock(&mutex);
    }
}
@end
//输出结果
2021-06-03 21:59:28.506002+0800 DJTestDemo[1499:56895] 剩余 = 4,{number = 6, name = (null)}
2021-06-03 21:59:29.509094+0800 DJTestDemo[1499:56895] 剩余 = 3,{number = 6, name = (null)}
2021-06-03 21:59:30.512722+0800 DJTestDemo[1499:56895] 剩余 = 2,{number = 6, name = (null)}
2021-06-03 21:59:31.517943+0800 DJTestDemo[1499:56895] 剩余 = 1,{number = 6, name = (null)}
2021-06-03 21:59:32.519213+0800 DJTestDemo[1499:56895] 剩余 = 0,{number = 6, name = (null)}
2021-06-03 21:59:33.522601+0800 DJTestDemo[1499:56895] 售完,{number = 6, name = (null)}
(4)os_unfair_lock
#import "ViewController.h"
#import //需要导入头文件
@interface ViewController ()
@property(nonatomic,assign)NSInteger tickets;
@property(nonatomic,assign)os_unfair_lock unfairLock;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tickets = 5;
    self.unfairLock = OS_UNFAIR_LOCK_INIT;
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [weakSelf saleTickets];
    });
    
}
-(void)saleTickets{
    while (1) {
        //加锁
        os_unfair_lock_lock(&_unfairLock);
        [NSThread sleepForTimeInterval:1];
        if (self.tickets > 0) {
            self.tickets--;
            NSLog(@"剩余 = %ld,%@",self.tickets,[NSThread currentThread]);
        }else{
            NSLog(@"售完,%@",[NSThread currentThread]);
            break;
        }
        //解锁
        os_unfair_lock_unlock(&_unfairLock);
    }
}
@end
//输出结果
2021-06-04 21:43:12.127220+0800 DJTestDemo[1195:39049] 剩余 = 4,{number = 6, name = (null)}
2021-06-04 21:43:13.131508+0800 DJTestDemo[1195:39049] 剩余 = 3,{number = 6, name = (null)}
2021-06-04 21:43:14.133489+0800 DJTestDemo[1195:39049] 剩余 = 2,{number = 6, name = (null)}
2021-06-04 21:43:15.138706+0800 DJTestDemo[1195:39049] 剩余 = 1,{number = 6, name = (null)}
2021-06-04 21:43:16.139372+0800 DJTestDemo[1195:39049] 剩余 = 0,{number = 6, name = (null)}
2021-06-04 21:43:17.143238+0800 DJTestDemo[1195:39049] 售完,{number = 6, name = (null)}
2.自旋锁

加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用一种忙等待,直等待锁定代码执行完成。相当于不停尝试执行代码,比较消耗性能。自旋锁避免了进城上下文的调度开销,如果只是阻塞很短时间的场合是有效的。

(1)OSSpinLock

由于优先级反转问题(如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。)在iOS10中被被os_unfair_lock替代。

(2)属性atomic

原子操作是同步的一个简单的形式,它处理简单的数据类型。原子操作的优势是 它们不妨碍竞争的线程。对于简单的操作,比如递增一个计数器,原子操作比使用锁 具有更高的性能优势。
属性atomic本身就是一把自旋锁。

  • atomic:原子属性,线程安全的,同一时间只有一个线程能够写入,但是同一时间可以有多个线程可以读取值。
  • nonatomic:非原子性,非线程安全,同一时间可以有多个线程读写。

atomic只是保证了gettersetter存取方法的线程安全,并不能保证整个对象是线程安全的,因此,线程安全还要开发者自己来处理
相对nonatomic来说atomic更消耗资源且速度慢,因此,如果没有特殊的需求,nonatomic是更好的选择

3.读写锁

也称共享-互斥锁、多读者-单写者锁
计算机程序的并发控制的一种同步机制,用于解决多线程对公共资源读写问题。读操作可并发,写操作是互斥的。读写锁通常用互斥锁、条件变量、信号量实现。

pthread_rwlock
#import "ViewController.h"
#import 
@interface ViewController ()
@property (nonatomic,strong) NSMutableString *paper;
@property (nonatomic,assign) int soldCount;
@end
@implementation ViewController
pthread_rwlock_t rwLock;

-(void)viewDidLoad{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    [self forTest];
}

- (void)forTest{
    self.paper = [NSMutableString stringWithCapacity:1];
    pthread_rwlock_init(&rwLock, NULL);
    
    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(writePaper) object:nil];
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(readPaper) object:nil];
    [thread2 start];
    
  
}
-(void)readPaper{
    pthread_rwlock_rdlock(&rwLock);
    NSLog(@"====开始读取,%@,%@",self.paper,[NSThread currentThread]);
    sleep(2);
    NSLog(@"====结束读取,%@",self.paper);
    pthread_rwlock_unlock(&rwLock);
    [self readPaper];
}

- (void)writePaper{
    pthread_rwlock_wrlock(&rwLock);
    NSLog(@"开始写入,%@",[NSThread currentThread]);
    sleep(2);
    [self.paper appendString:@"1"];
    [self.paper appendString:@"1"];
    [self.paper appendString:@"1"];
    NSLog(@"结束写入");
    pthread_rwlock_unlock(&rwLock);
    [self writePaper];
}
@end
//输出结果
2021-06-08 14:36:52.699584+0800 DJTestDemo[3551:238023] 开始写入,{number = 8, name = (null)}
2021-06-08 14:36:54.703809+0800 DJTestDemo[3551:238023] 结束写入
2021-06-08 14:36:54.704585+0800 DJTestDemo[3551:238024] ====开始读取,111,{number = 9, name = (null)}
2021-06-08 14:36:56.707551+0800 DJTestDemo[3551:238024] ====结束读取,111
2021-06-08 14:36:56.708200+0800 DJTestDemo[3551:238023] 开始写入,{number = 8, name = (null)}
2021-06-08 14:36:58.708503+0800 DJTestDemo[3551:238023] 结束写入
2021-06-08 14:36:58.708986+0800 DJTestDemo[3551:238024] ====开始读取,111111,{number = 9, name = (null)}
2021-06-08 14:37:00.709296+0800 DJTestDemo[3551:238024] ====结束读取,111111
2021-06-08 14:37:00.709553+0800 DJTestDemo[3551:238023] 开始写入,{number = 8, name = (null)}
2021-06-08 14:37:02.712721+0800 DJTestDemo[3551:238023] 结束写入
2021-06-08 14:37:02.713429+0800 DJTestDemo[3551:238024] ====开始读取,111111111,{number = 9, name = (null)}
2021-06-08 14:37:04.718498+0800 DJTestDemo[3551:238024] ====结束读取,111111111
2021-06-08 14:37:04.719302+0800 DJTestDemo[3551:238023] 开始写入,{number = 8, name = (null)}
2021-06-08 14:37:06.724041+0800 DJTestDemo[3551:238023] 结束写入
......
4.GCD信号量

使用GCD信号量为线程加锁,相见GCD篇信号量。

@interface ViewController ()
@property(nonatomic , assign)int number;
@property(nonatomic , strong)dispatch_semaphore_t semaphore;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor orangeColor];
    self.number = 2;
    self.semaphore = dispatch_semaphore_create(1);
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf numberChange];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf numberChange];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf numberChange];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf numberChange];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf numberChange];
    });
}

-(void)numberChange{
    __weak typeof(self) weakSelf = self;
    //修改number,线程安全
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    if (weakSelf.number > 0) {
        weakSelf.number -- ;
        NSLog(@"%d = %@",weakSelf.number,[NSThread currentThread]);
    }else{
        NSLog(@"结束 -- %@",[NSThread currentThread]);
    }
    dispatch_semaphore_signal(weakSelf.semaphore);
}
@end
//输出结果
2021-05-25 15:08:08.528786+0800 DJGCDDemo[10226:142521] 1 = {number = 6, name = (null)}
2021-05-25 15:08:08.528953+0800 DJGCDDemo[10226:142524] 0 = {number = 7, name = (null)}
2021-05-25 15:08:08.529088+0800 DJGCDDemo[10226:142528] 结束 -- {number = 5, name = (null)}
2021-05-25 15:08:08.529204+0800 DJGCDDemo[10226:142523] 结束 -- {number = 4, name = (null)}
2021-05-25 15:08:08.529319+0800 DJGCDDemo[10226:142526] 结束 -- {number = 3, name = (null)}
5.条件锁

就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源分配到了,条件锁打开,进行继续执行。
NSConditionLock

    NSConditionLock *lock = [[NSConditionLock alloc]initWithCondition:0];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:1];
        NSLog(@"1 = %@",[NSThread currentThread]);
        sleep(2);
        [lock unlock];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        if ([lock tryLockWhenCondition:0]) {
            NSLog(@"2 = %@",[NSThread currentThread]);
            [lock unlockWithCondition:2];
            NSLog(@"2解锁成功");
        }else{
            NSLog(@"2尝试加锁失败");
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"3 = %@",[NSThread currentThread]);
            [lock unlock];
            NSLog(@"3解锁成功");
        }else{
            NSLog(@"3尝试加锁失败");
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"4 = %@",[NSThread currentThread]);
            [lock unlockWithCondition:1];
            NSLog(@"4解锁成功");
        }else{
            NSLog(@"4尝试加锁失败");
        }
    });
    
}
//输出结果
2021-06-08 15:05:05.457243+0800 DJTestDemo[3780:253462] 2 = {number = 7, name = (null)}
2021-06-08 15:05:05.457802+0800 DJTestDemo[3780:253462] 2解锁成功
2021-06-08 15:05:06.455148+0800 DJTestDemo[3780:253464] 3 = {number = 3, name = (null)}
2021-06-08 15:05:06.455392+0800 DJTestDemo[3780:253464] 3解锁成功
2021-06-08 15:05:07.454496+0800 DJTestDemo[3780:253461] 4 = {number = 4, name = (null)}
2021-06-08 15:05:07.455178+0800 DJTestDemo[3780:253461] 4解锁成功
2021-06-08 15:05:07.455230+0800 DJTestDemo[3780:253459] 1 = {number = 5, name = (null)}

先输出了 "2",因为"1" 的加锁条件不满足,初始化时候的condition参数为 0,而加锁条件是condition为 1,所以加锁失败。lockWhenConditionlock方法类似,加锁失败会阻塞线程,所以线程 1 会被阻塞着。
tryLockWhenCondition方法就算条件不满足,也会返回NO,不会阻塞当前线程。"2"执行了 [lock unlockWithCondition:2]; 所以condition被修改成了 2。
而"3" 的加锁条件是condition为 2, 所以"3"才能加锁成功,执行 [lock unlock]; 解锁成功且不改变condition值。
"4"的条件也是 2,所以也加锁成功,解锁时将 condition 改成 1。这个时候"1"终于可以加锁成功,解除了阻塞。
从上面可以得出,NSConditionLock还可以实现任务之间的依赖。

  • lock期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition)那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁), 则等待,直至其他线程解锁。
    lockWhenCondition: 如果没有其他线程获得该锁,但是该锁内部的condition不等于条件,它依然不能获得锁,仍然等待。如果内部的condition等于条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
  • unlockWithCondition:A释放锁,同时把内部的condition设置为A
  • lockWhenCondition: beforeDate:如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态, 这个函数的目的在于可以实现两种状态下的处理。
6.递归锁

定义的锁可以在同一线程多次lock,不会造成死锁。递归锁会根据它被lock多少次。每次成功lock都必须平衡调用unlock操作。只有所有的锁住和解锁平衡的时候,锁才真正被释放给其他线程获得。
使用锁最容易犯的一个错误就是在递归或者循环中造成死锁。
NSRecursiveLock

#import "DJViewController.h"
@interface DJViewController ()
@property (nonatomic,strong) NSThread *thread;
@property (nonatomic,strong) NSRecursiveLock *lock;
@end

@implementation DJViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(addObject) object:NULL];
    [self.thread start];
    self.lock = [[NSRecursiveLock alloc] init];
}

-(void)addObject{
    [self sumTotal:10];
}
-(void)sumTotal:(long)value{

   [self.lock lock];
    if(value >0 ){
        value -- ;
        NSLog(@"%ld",value);
        [self sumTotal:value];
    }
    [self.lock unlock];
}
@end
//输出结果
2021-06-08 15:29:40.405564+0800 DJTestDemo[3902:265752] 9
2021-06-08 15:29:40.405867+0800 DJTestDemo[3902:265752] 8
2021-06-08 15:29:40.406077+0800 DJTestDemo[3902:265752] 7
2021-06-08 15:29:40.406468+0800 DJTestDemo[3902:265752] 6
2021-06-08 15:29:40.406732+0800 DJTestDemo[3902:265752] 5
2021-06-08 15:29:40.407104+0800 DJTestDemo[3902:265752] 4
2021-06-08 15:29:40.407174+0800 DJTestDemo[3902:265752] 3
2021-06-08 15:29:40.407394+0800 DJTestDemo[3902:265752] 2
2021-06-08 15:29:40.407604+0800 DJTestDemo[3902:265752] 1
2021-06-08 15:29:40.407963+0800 DJTestDemo[3902:265752] 0
7.断言

NSCondition是一种特殊类型的锁,可以实现不同线程的调度。一个线程被某一个条件阻塞,直到另一个线程满足该条件从而发送信号给该线程,使得该线程可以正确执行。

    NSCondition *conditionLock = [[NSCondition alloc]init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionLock lock];
        NSLog(@"A加锁");
        //挂起线程
        [conditionLock wait];
        NSLog(@"A唤醒");
        [conditionLock unlock];
        NSLog(@"A解锁");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionLock lock];
        NSLog(@"B加锁");
        [conditionLock wait];
        NSLog(@"B唤醒");
        [conditionLock unlock];
        NSLog(@"B解锁");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        //唤醒一条线程
//        [conditionLock signal];
        //唤起所有线程
        [conditionLock broadcast];
    });
//输出结果
2021-06-09 10:32:24.669638+0800 DJTestDemo[1628:48574] B加锁
2021-06-09 10:32:24.670001+0800 DJTestDemo[1628:48572] A加锁
2021-06-09 10:32:26.669922+0800 DJTestDemo[1628:48574] B唤醒
2021-06-09 10:32:26.670141+0800 DJTestDemo[1628:48574] B解锁
2021-06-09 10:32:26.670151+0800 DJTestDemo[1628:48572] A唤醒
2021-06-09 10:32:26.670333+0800 DJTestDemo[1628:48572] A解锁

开启 3 个线程顺序打印1 - 100

- (void)print0_100 {
    __block int i = 0;
    NSCondition *condition = [[NSCondition alloc] init];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [condition lock];
            while (i%3 != 0) {
                [condition wait];
            }
            if (i > 100) {
                [condition unlock];
                return;
            }
            NSLog(@"A ==== i = %d",i);
            i++;
            [condition broadcast];
            [condition unlock];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [condition lock];
            while (i%3 != 1) {
                [condition wait];
            }
            if (i > 100) {
                [condition unlock];
                return;
            }
            NSLog(@"B ==== i = %d",i);
            i++;
            [condition broadcast];
            [condition unlock];
        }
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [condition lock];
            while (i%3 != 2) {
                [condition wait];
            }
            if (i > 100) {
                [condition unlock];
                return;
            }
            NSLog(@"C ==== i = %d",i);
            i++;
            [condition broadcast];
            [condition unlock];
        }
    });

}
8.死锁
(1)产生死锁的四个必要条件
  • 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。
  • 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但又对自己获得的资源保持不放。
  • 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放。
  • 环路等待条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链。
(2)解决办法
  • 鸵鸟策略:当系统发生死锁时不会对用户造成多大影响,或系统很少发生死锁的场合采用允许死锁发生的鸵鸟策略,这样一来可能开销比不允许发生死锁及检测和解除死锁的小。如果死锁很长时间才发生一次,而系统每周都会因硬件故障、编译器错误或操作系统错误而崩溃一次,那么大多数工程师不会以性能损失或者易用性损失的代价来设计较为复杂的死锁解决策略,来消除死锁。大多数操作系统,包括UNIX,LINUX和windows,处理死锁问题的办法仅仅是忽略它。其假设前提是大多数用户宁可在极偶然的情况下发生死锁也不愿接受因为死锁解决算法带来的性能上的损失。因为解决死锁的问题,通常代价很大。鸵鸟策略的实质:出现死锁的概率很小,并且出现之后处理死锁会花费很大的代价,还不如不做处理,OS中这种置之不理的策略称之为鸵鸟策略(也叫鸵鸟算法)。
  • 预防策略
  • 避免策略
  • 检测与解除死锁
    https://blog.csdn.net/daocaokafei/article/details/125531152

你可能感兴趣的:(iOS 多线程(五) - 线程同步)