2019 -----多线程相关之GCD、死锁、dispatch_barrier_async、dispatch_group_async、Dispatch Semaphore、dispa...

  • GCD 同步/异步 , 串行/并发
  • 死锁
  • GCD 中的执行顺序
  • dispatch_barrier_async
  • dispatch_group_async
  • dispatch_after延时函数
  • dispatch_once实现单例
  • dispatch Semaphore

一、GCD---队列

iOS中,有GCD、NSOperation、NSThread等几种多线程技术方案。

而GCD共有三种队列类型:
main queue:通过dispatch_get_main_queue()获得,这是一个与主线程相关的串行队列。

global queue:全局队列是并发队列,由整个进程共享。存在着高、中、低三种优先级的全局队列。调用dispath_get_global_queue并传入优先级来访问队列。

自定义队列:通过函数dispatch_queue_create创建的队列

二、 死锁

死锁就是队列引起的循环等待

1、一个比较常见的死锁例子:主队列同步

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_sync(dispatch_get_main_queue(), ^{
   
    NSLog(@"deallock");
    });
}

在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。
想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。

2、下面的也会造成死锁

dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{
   
    dispatch_sync(serialQueue, ^{
        
        NSLog(@"deadlock");
    });
});

外面的函数无论是同步还是异步都会造成死锁
这是因为里面的任务和外面的任务都在同一个 serialQueue 队列内, 又是同步, 这就和上边主队列同步的例子是一样的造成了死锁
解决方法和上面一样, 将同步改成异步或者换成串行队列, 都可以解决

   dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

   dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{
   
    dispatch_sync(serialQueue2, ^{
        
        NSLog(@"deadlock");
    });
});

这样是不会死锁的,并且serialQueue和serialQueue2是在同一个线程中的。

三、GCD 任务执行顺序

1、串行队列先异步后同步

dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

NSLog(@"1");

dispatch_async(serialQueue, ^{
    
     NSLog(@"2");
});

NSLog(@"3");

dispatch_sync(serialQueue, ^{
    
    NSLog(@"4");
});

NSLog(@"5");

打印顺序是13245
原因是:
首先先打印1
接下来将任务2其添加至串行队列上,由于任务2是异步,不会阻塞线程,继续向下执行,打印3
然后是任务4,将任务4添加至串行队列上,因为任务4和任务2在同一串行队列,根据队列先进先出原则,任务4必须等任务2执行后才能执行,又因为任务4是同步任务,会阻塞线程,只有执行完任务4才能继续向下执行打印5
所以最终顺序就是13245。
这里的任务4在主线程中执行,而任务2在子线程中执行。
如果任务4是添加到另一个串行队列或者并行队列,则任务2和任务4无序执行(可以添加多个任务看效果)

2、performSelector

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    [self performSelector:@selector(test:) withObject:nil afterDelay:0];
});

这里的test方法是不会去执行的,原因在于

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

这个方法要创建提交任务到runloop上的,而gcd底层创建的线程是默认没有开启对应runloop的,所有这个方法就会失效。
而如果将dispatch_get_global_queue改成主队列,由于主队列所在的主线程是默认开启了runloop的,就会去执行(将dispatch_async改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test方法也是会去执行的)。

两个方法: 一个是开启当前的线程, 二个是改成主队列

四、dispatch_barrier_async

多读单写的意思就是:可以多个读者同时读取数据,而在读的时候,不能去写入数据。并且,在写的过程中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的


image.png

这里的写处理就是通过栅栏的形式去写。
就可以用dispatch_barrier_sync(栅栏函数)去实现

2、dispatch_barrier_sync的用法:

dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    for (NSInteger i = 0; i < 10; i++) {
    
        dispatch_sync(concurrentQueue, ^{
        
            NSLog(@"%zd",i);
        });
    }

    dispatch_barrier_sync(concurrentQueue, ^{
   
        NSLog(@"barrier");
    });

    for (NSInteger i = 10; i < 20; i++) {
    
        dispatch_sync(concurrentQueue, ^{
        
            NSLog(@"%zd",i);
        });
    }

这里的dispatch_barrier_sync上的队列要和需要阻塞的任务在同一队列上,否则是无效的。
从打印上看,任务0-9和任务任务10-19因为是异步并发的原因,彼此是无序的。而由于栅栏函数的存在,导致顺序必然是先执行任务0-9,再执行栅栏函数,再去执行任务10-19。

  • dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一个栅栏函数在执行中,它会等待栅栏函数执行完)
  • dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一个栅栏函数在异步执行中,它会立马返回)
    而dispatch_barrier_sync和dispatch_barrier_async的区别也就在于会不会阻塞当前线程
    比如,上述代码如果在dispatch_barrier_async后随便加一条打印,则会先去执行该打印,再去执行任务0-9和栅栏函数;而如果是dispatch_barrier_sync,则会在任务0-9和栅栏函数后去执行这条打印。

3、则可以这样设计多读单写:

- (id)readDataForKey:(NSString *)key{
    __block id result;

    dispatch_sync(_concurrentQueue, ^{
   
        result = [self valueForKey:key];
    });

    return result;
}

- (void)writeData:(id)data forKey:(NSString *)key{
    dispatch_barrier_async(_concurrentQueue, ^{
   
        [self setValue:data forKey:key];
    });
}

五、dispatch_group_async

场景:在n个耗时并发任务都完成后,再去执行接下来的任务。比如,在n个网络请求完成后去刷新UI页面。

dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_t group = dispatch_group_create();

    for (NSInteger i = 0; i < 10; i++) {
    
        dispatch_group_async(group, concurrentQueue, ^{
        
            sleep(1);
        
            NSLog(@"%zd:网络请求",i);
        });
    }

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
        NSLog(@"刷新页面");
    });

六、Dispatch Semaphore

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。

Dispatch Semaphore 提供了三个函数

1.dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
2.dispatch_semaphore_signal:发送一个信号,让信号总量加1
3.dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
Dispatch Semaphore 在实际开发中主要用于:

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁

1、保持线程同步:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block NSInteger number = 0;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        number = 100;
    
        dispatch_semaphore_signal(semaphore);
});

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    NSLog(@"semaphore---end,number = %zd",number);
  • dispatch_semaphore_wait加锁阻塞了当前线程,
  • dispatch_semaphore_signal解锁后当前线程继续执行

2、保证线程安全,为线程加锁:
在线程安全中可以将dispatch_semaphore_wait看作加锁,而dispatch_semaphore_signal看作解锁
首先创建全局变量

_semaphore = dispatch_semaphore_create(1);

注意到这里的初始化信号量是1

- (void)asyncTask{ 
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

    count++;

    sleep(1);

    NSLog(@"执行任务:%zd",count);

    dispatch_semaphore_signal(_semaphore);
}

六、延时函数(dispatch_after)

dispatch_after能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue

//第一个参数是time,第二个参数是dispatch_queue,第三个参数是要执行的block
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
     NSLog(@"dispatch_after");
  });

由于其内部使用的是dispatch_time_t管理时间,而不是NSTimer。
所以如果在子线程中调用,相比performSelector:afterDelay,不用关心runloop是否开启

七、使用dispatch_once实现单例

+ (instancetype)shareInstance {

    static dispatch_once_t onceToken;

    static id instance = nil;

    dispatch_once(&onceToken, ^{
    
        instance = [[self alloc] init];
    });

    return instance;
}

你可能感兴趣的:(2019 -----多线程相关之GCD、死锁、dispatch_barrier_async、dispatch_group_async、Dispatch Semaphore、dispa...)