美文网首页
iOS多线程及线程同步方案(线程锁)总结

iOS多线程及线程同步方案(线程锁)总结

作者: 天明天 | 来源:发表于2019-05-14 16:27 被阅读0次

一. 多线程

1.常见多线程方案

pthread : 纯粹 C 语言的API,跨平台, 线程生命周期程序员管理,几乎不用
NSThread: OC 面向对象 API ,简单易用,可以直接操作线程对象 线程生命周期程序员管理,偶尔使用
GCD:旨在替代NSThread,充分利用 设备多核,C 语言的API,线程生命周期自动管理 经常使用
NSOPeration:基于 GCD (底层就是GCD)比GCD多了些实用功能,OC 方法,自动管理线程,经常实用。

2. GCD的基础知识
(1)常用函数:

dispatch_sync():同步执行函数,不会开辟新线程。
dispatch_async():异步执行函数,具备开辟新线程的能力,但是不一定会开辟新线程执行函数。
异步执行任务 方式:

dispatch_queue_t  queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"执行任务-----%@",[NSThread currentThread]);
    });
(2)GCD队列

GCD 队列分为:串行队列并行队列主队列

  • 串行队列创建方式:
dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_SERIAL);
// 串行执行,第一个参数是名称 随便写,第二个是标识:DISPATCH_QUEUE_SERIAL

注意:这里虽然是C语言的API,并且有create关键字,但是这里并不需要释放。

  • 并发队列创建方式有两种:
    第一种:全局的并发队列:
dispatch_quque_t queue = dispatch_get_global_queue(0,0);

第二种: 手动创建并发队列

dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_CONCURRENT);
// 串行执行,第一个参数是名称 随便写,第二个是标识:DISPATCH_QUEUE_CONCURRENT,并发队列标识
  • 主队列 是一种特殊的串行队列
dispatch_queue_t queue = dispatch_get_main_queue();
(3)同步、异步、串行、并发
  • 同步跟异步:主要是方法区别 dispatch_sync() 同步 跟 dispatch_async() 异步方法。两者的区别是 是否能开辟新的线程执行任务。
    image.png
  • 队列的类型决定了任务的执行方式:串行跟并发
    注意 dispatch_async() 具备开辟 新线程的能力,但是不表示 使用它就一定会开辟新的线程。 例如 传入的 queue 是主队列,就是在主线程中执行任务,没有开辟新线程。
    image.png
(4)线程死锁

发生线程死锁的条件:

使用 dispatch_sync() 函数往当前 串行队列里面添加任务就会产生死锁。
几个典型的 死锁案例:

dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2");
    });// 往主线程里面 同步添加任务 会发生死锁现象
NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });//此时会发生线程死锁
    
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");

注意:如果把 “执行任务3”代码改为:

  dispatch_sync(dispatch_get_main_queue(), ^{ 
            NSLog(@"执行任务3");
        }); //此时不会发生线程死锁,因为任务在两个线程中执行(一个主线程、一个子线程)。
(5)GCD队列组
  • 队列组创建 dispatch_group_t groupt = dispatch_group_create();
  • 创建并发队列 dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
  • 并发执行任务:
// 并发执行任务
    dispatch_group_async(groupt, queue, ^{
       
        NSLog(@"任务1 ---%@",[NSThread currentThread]);
        NSLog(@"任务2 ---%@",[NSThread currentThread]);

    });
  • 任务执行完成后回调 会通知这个方法 dispatch_group_notify()
 //等前面的任务 并发完成后 执行 notify
    dispatch_group_notify(groupt, queue, ^{
        //回到主线程队列
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"任务3 ---%@",[NSThread currentThread]);
        });

    });//也可以这样写
  dispatch_group_notify(groupt, dispatch_get_main_queue(), ^{
        //回到主线程队列 
       NSLog(@"任务3 ---%@",[NSThread currentThread]);
    })
(6)多线程隐患
  • 一段资源同时被 多条线程访问,会引发数据混乱。
  • 多个线程同时访问同一个对象、变量、文件会发生数据错误。

二. 线程同步方案(线程锁)
OC 线程加锁方案:

(1) OSSPinLock: 自旋锁
(2) os_unfair_lock : 互斥锁
(3) pthread_mutex:跨平台加锁方案
(4) dispatch_semaphore:推荐用法
(5) 串行队列: dispatch_create(“myQueue”,DISPATCH_QUEUE_SERIAL)
(6) NSLock :就是对pthread_metux 的封装
(7) NSRecursiveLock: 递归锁
(8) NSCondition; 条件锁
(9) NSConditionLock: 顺序执行
(10) @synchronized:最简单的写法
分别介绍:

1.OSSPinLock: 自旋锁, 性能很高,但是不推荐使用.

导入头文件:#import <libkern/OSAtomic.h>
创建锁:
@property (assign, nonatomic) OSSpinLock lock;
// 初始化锁
self.lock = OS_SPINLOCK_INIT;
加锁:OSSpinLockLock(&_lock);
添加关键代码<><><>
解锁:OSSPinLockUnLock(&_lock)
注意: lock 要做成 全局变量,要使用同一锁才可以。如果有两个方法:判断两个方法 是否能同时执行,如果不能同时执行 则需要共用一把锁。
原理:第二条线程 会等待 (此时会存在线程等待)第一条线程解锁,才会继续执行。
p.s.OSSpinLock 的线程等待会处于忙等状态 (while(1);)会一直占有CPU 的资源 并没有睡觉 休息。 目前此 锁已经不安全了 会出现问题:线程优先级反转问题。ios10 以后使用会警告️。

2. os_unfair_lock 现在它代替 OSSpinLock iOS 10 以后系统使用

导入头文件:import<os/lcok.h>
注意:os_unfair_lock 是一个C语言的结构体,如果要使用属性调用需要使用assign修饰
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
使用方法:初始化、加锁、解锁

// 初始化锁
self.ticketLock = OS_UNFAIR_LOCK_INIT;
- (void)__saleTicket
{
// 加锁
    os_unfair_lock_lock(&_ticketLock);
    [super __saleTicket];
  //解锁
    os_unfair_lock_unlock(&_ticketLock);
}

注意:如果忘记解锁,会出现线程死锁现象。

3. pthread_mutex :多平台通用,跨平台:互斥锁。等待锁的线程或处于休眠状态。区别于 自旋锁(OSSpinLock),自旋锁不会使线程休眠.

使用方式:导入头文件:#import<pthread.h>
静态初始化:pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER
但是不能直接 赋值给类属性,因为 PTHREAD_MUTEX_INITIALIZER 是一个结构体。
动态初始化方式:
@property (assign, nonatomic) pthread_mutex_t ticketMutex;

//    初始化属性
//    pthread_mutexattr_t attr;
//    pthread_mutexattr_init(&attr);
//    设置属性, PTHREAD_MUTEX_DEFAULT是默认锁的属性模式
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
//    // 初始化锁,传入属性的地址
//    pthread_mutex_init(&ticketMutex, &attr);
//    // 销毁属性
//    pthread_mutexattr_destroy(&attr);

加锁:pthread_mutex_lock(&ticketMutex)
关键代码:<><>
解锁:pthread_mutex_unlock(&ticketMutex)
注意: 这里的属性设置pthread_mutex_init(&ticketMutex, NULL); 可以传NULL,默认就等同于:PTHREAD_MUTEX_DEFAULT

最后注意把mutex 销毁掉

- (void)dealloc
{
    pthread_mutex_destroy(&_moneyMutex);
    pthread_mutex_destroy(&_ticketMutex);
}

递归锁:一直调用一个关键方法函数,又需要加锁 则需要递归锁。方法:只需要改变 mutex的属性即可: 修改为:PTHREAD_MUTEX_RECURSIVE

 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

递归锁:允许 同一个线程对一把锁进行重复加锁。
pthread_mutex : 条件 用法 条件锁。
pthread_cond_t :条件
pthread_cond_t:(&条件,锁);

条件锁:线程1 跟 线程 2,线程1 需要等待线程2 做完一些事情后 唤醒 线程1。所以需要 等待(pthread_cond_wait),然后 线程2 执行唤醒操作: pthread_cond_signal(&条件)
用途:

  pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    pthread_mutex_unlock(&_mutex);

pthread_cond_signal(&条件) 唤醒线程操作,激活等待这个 条件(cond)的线程。
pthread_cond_broadcast(&cond), 唤醒所有等待这个条件(cond)的线程。

4.NSLock ,NSRecursiveLock

NSlock : 底层就是对pthread_mutex 普通锁(PTHERAD_MUTEX_NORMAL)的封装 也是互斥锁
NSRecursiveLock :递归锁,底层也是对 pthread_mutex 的封装,只不过他是对 mutex 递归锁(PTHREAD_MUTEX_RECURSIVE)的封装。
使用:

@property (strong, nonatomic) NSLock *ticketLock;
//初始化
self.ticketLock = [[NSLock alloc] init];
// 加锁
 [self.ticketLock lock];
   //关键代码
    [super __saleTicket];
 //解锁  
    [self.ticketLock unlock];
5.NSCondition 是对 mutex跟cond条件的封装,内部存在锁

使用方法:

#import "NSConditionDemo.h"

@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo
- (instancetype)init
{
    if (self = [super init]) {
// 初始化
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{//两条线程 执行方法
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 线程1
// 删除数组中的元素
- (void)__remove
{
//加锁
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待信号
        [self.condition wait];
    }
   [self.data removeLastObject];
    NSLog(@"删除了元素");
    //解锁
    [self.condition unlock];
}
// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 发送信号
    [self.condition signal];
    
    // 广播
//    [self.condition broadcast];
    [self.condition unlock];
    
}
6.NSConditionLock : 条件锁,对NSCondition的进一步的封装。增加条件的 值 的设置

使用:能做到线程依赖,根据条件值 :1—>2—>3 方法顺序执行。
直接调用 lock 方法,则表明直接加锁,不用等条件值
子线程 顺序执行 串行
初始化:

NSConditionLock * lock = [NSConditionLock alloc] initWithCondition: 1 ];
- (void)otherTest // 三条线程
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one
{
    [self.conditionLock lock];// 直接加锁,不用等条件值
    
    NSLog(@"__one");
    sleep(1);
   [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];  
    NSLog(@"__two");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three
{
    [self.conditionLock lockWhenCondition:3];
   NSLog(@"__three");
    
    [self.conditionLock unlock];// 最后执行解锁
}
7.dispatch_queue 直接使用 GCD的串行队列 也可以实现线程同步
8. dispatch_semaphore: semaphore 信号量,信号量的初始值可以用来控制线程并发访问的最大数量。如果设置为 1 则为串行 执行,简介达到 线程同步的目的

// 控制同时执行线程的的数量
使用方法:

// 信号量
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
// 初始化。最大并发数是 1 
 self.semaphore = dispatch_semaphore_create(1);

- (void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

// 线程
- (void)test
{
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码// DISPATCH_TIME_FOREVER 这个是 一直等待的意思
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}
9.@synchronized :是对 pthread_mutex 递归锁的封装,是最简单的 线程同步方法.

使用方法:没有提示 ,因为性能较差,apple 不推荐使用

@synchronized( self // 锁对象){
//关键代码
}
// 括号 里面 写锁 对象,根据 括号里面的对象 作为锁对象。同一个代表同一个锁,支持递归加锁。

三.总结

性能排行.png
1.推荐使用:dispatch_samephorepthread_mutex 方案,因为OSSpinLock 有安全隐患不推荐使用了,os_unfair_lock 只有 ios 10 以后才可以使用。也可以 iOS10 之前使用上面两种方案,iOS10 以后使用 os_unfair_lock
2.atomicnonatomic 原子属性

atomic: 原子属性,给属性加上 atomic 修饰,相当于属性的gettersetter方法都是原子属性操作,即:settergetter方法 都是线程同步的,就是加锁。
但是:他不能保证使用属性的过程是线程安全的。
比如:使用 属性 @porperty(atomic,strong) NSMutableArray *dataArr;
使用 p.data 是线程安全的,但是[p.data addobject:@”1”]; 往里面添加 object 就是不线程安全的了。
不使用 atomic 的原因 是 太消耗性能了,经常在 Mac 编程中使用。

3. iOS中的 读写安全方案

文件(IO)操作:读取文件,与写入操作。要求:写入文件:只允许一条线程,读取文件 可以允许多条线程。 多读单写 实现

image.png

解决方案:pthread_rwlcok :读写锁
dispatch_barrier_async: 异步栅栏操作

  • pthread_rwlcok使用方式: pthread_rwlcok使用.png
    使用方式:
    dispatch_barrier_async 异步栅栏操作:
  • 这个函数传入的并发队列必须是自己通过dispatch_queue_create()创建的。
  • 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果。
    使用:
 self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);//如果是串行跟全局并发队列 则不起作用
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(self.queue, ^{
            [self read];
        });
       dispatch_async(self.queue, ^{
            [self read];
        });
       dispatch_async(self.queue, ^{
            [self read];
        });
       dispatch_barrier_async(self.queue, ^{
            [self write];
        });
    }
- (void)read {
    sleep(1);
    NSLog(@"read");
}
- (void)write
{
    sleep(1);
    NSLog(@"write");
}

相关文章

  • 起底多线程同步锁(iOS)

    起底多线程同步锁(iOS) 起底多线程同步锁(iOS)

  • OC--各种线程锁

    参考:正确使用多线程同步锁@synchronized()iOS中的锁iOS多线程安全详解iOS 常见知识点(三):...

  • iOS多线程及线程同步方案(线程锁)总结

    一. 多线程 1.常见多线程方案 pthread : 纯粹 C 语言的API,跨平台, 线程生命周期程序员管理...

  • 线程锁

    探讨iOS开发中各种锁使用NSCondition实现多线程同步 NSCondition是线程同步, 阻塞线程。 取...

  • 细数iOS中的线程同步方案(一)

    细数iOS中的线程同步方案(一)细数iOS中的线程同步方案(二) 多线程安全问题 多个线程可能访问同一块资源,比如...

  • iOS中的锁

    起底多线程同步锁(iOS) OSSpinLock NSLock NSRecursiveLock 同步 NSCond...

  • iOS多线程--并行开发二

    接上文iOS多线程--并行开发一 4、线程同步 说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程...

  • iOS多线程之NSThread

    前面总结了多线程基本概念和iOS多线程PThread的使用,下面接着总结iOS多线程的另外一种实现方案NSThre...

  • iOS多线程详解

    一 iOS多线程介绍 二 线程同步方案 一 iOS多线程介绍 首先我们先了解一下关于线程的几个概念: 1 什么是进...

  • iOS线程同步

    线程同步 提到多线程大家肯定会提到锁,其实真正应该说的是多线程同步,锁只是多线程同步的一部分。 多线程对于数据处理...

网友评论

      本文标题:iOS多线程及线程同步方案(线程锁)总结

      本文链接:https://www.haomeiwen.com/subject/joxsaqtx.html