GCD研究
ChenghuiBai Lv3

[TOC]

简介

1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
多线程技术可以提高程序的执行效率

作用

显示\刷新UI界面、处理UI事件(比如点击事件、滚动事件、拖拽事件等)在主线程执行。

耗时操作,开启子线程执行。

原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

如果线程非常多,CPU会在N多线程之间调度,会消耗大量CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。
因此我们一般只开3-5条线程。

优点

能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)

缺点

创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。
程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题。

多线程方案

在iOS中多线程方案有多中,但最常用的属GCD和NSOperation,然后NSThread、pthread很少使用。本文主要研究GCD这块内容。

GCD

Grand Central Dispatch (GCD), contains language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.

The BSD subsystem, Core Foundation, and Cocoa APIs have all been extended to use these enhancements to help both the system and your application to run faster, more efficiently, and with improved responsiveness. Consider how difficult it is for a single application to use multiple cores effectively, let alone to do it on different computers with different numbers of computing cores or in an environment with multiple applications competing for those cores. GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.

GCD全称为Grand Central Dispatch,包含语言功能,运行时库和系统增强功能,这些功能提供了系统的,全面的改进,以支持在macOS,iOS,watchOS和tvOS中的多核硬件上并发代码执行的支持。

BSD子系统,Core Foundation和Cocoa API均已扩展为使用这些增强功能,以帮助系统和您的应用程序更快,更高效地运行,并提高响应速度。单个应用程序有效地使用多个内核很困难,在具有不同数量计算内核的不同计算机上或在多个应用程序竞争那些内核的环境中进行操作就更困难了。在系统级别运行的GCD可以更好地满足所有正在运行的应用程序的需求,并以平衡的方式将它们与可用的系统资源进行匹配。

队列
串行

让任务串行执行(一个任务执行完毕后,再执行下一个任务)

并发

让多个任务并发执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效

dispatch_queue_set_specific

向指定队列里面设置一个标识

向queue1对了中设置一个queueKey1标识:

dispatch_queue_set_specific(queue1, queueKey1, &queueKey1,NULL);
dispatch_queue_get_specific

是获取指定调度队列的上下文键/值数据。

    const void * queueKey = "queueKey";
const void * queueKey2 = "queueKey2";
dispatch_queue_t queue = dispatch_queue_create(queueKey, NULL);
dispatch_queue_t queue2 = dispatch_queue_create(queueKey2, NULL);

//调用此方法会触发queueFunction函数,留个疑问queueFunction是在什么时候触发?
dispatch_queue_set_specific(queue, queueKey, &queueKey, queueFunction);
dispatch_queue_set_specific(queue2, queueKey2, &queueKey2, NULL);

dispatch_sync(queue, ^{
go();
});
dispatch_sync(queue2, ^{
go();
});

if (dispatch_queue_get_specific(queue, queueKey)) {
NSLog(@"__run in queue");
}

//main queue中找不到queueKey,所以这段Log不会触发,使用dispatch_get_specific(queueKey)的原理也一样
if (dispatch_queue_get_specific(dispatch_get_main_queue(), queueKey)) {
NSLog(@"__run in main queue");
}
if (dispatch_get_specific(queueKey)) {
NSLog(@"__run in main queue");
}


void go() {
//使用dispatch_sync改变了当前的执行队列,所以这里可以检索到queueKey
if(dispatch_get_specific("queueKey")) {
NSLog(@"queue");
} else if(dispatch_get_specific("queueKey2")) {
NSLog(@"queue2");
} else {
NSLog(@"main queue");
}
}

void queueFunction() {
NSLog(@"__queueFunction");
}
dispatch_get_specific

在当前队列中取出标识

注意iOS中线程和队列的关系,所有的动作都是在队列中执行的!

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
static void *queueKey1 = "queueKey1";

dispatch_queue_t queue1 = dispatch_queue_create(queueKey1, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, queueKey1, &queueKey1, NULL);

NSLog(@"1. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());

if (dispatch_get_specific(queueKey1)) {
//当前队列是主队列,不是queue1队列,所以取不到queueKey1对应的值,故而不执行
NSLog(@"2. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
}else{
NSLog(@"3. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
}

dispatch_sync(queue1, ^{
NSLog(@"4. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];

if (dispatch_get_specific(queueKey1)) {
//当前队列是queue1队列,所以能取到queueKey1对应的值,故而执行
NSLog(@"5. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
}else{
NSLog(@"6. 当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
}
});
dispatch_async(queue1, ^{
NSLog(@"7. t当前线程是: %@, 当前队列是: %@ 。",[NSThread currentThread],dispatch_get_current_queue());
[NSThread sleepForTimeInterval:1];
});

[NSThread sleepForTimeInterval:5];
}
return 0;
}

输出结果:

2016-02-19 14:31:23.390 gcd[96865:820267] 1.当前线程是: <NSThread: 0x1001053e0>{number = 1, name = main},当前队列是: <OS_dispatch_queue: com.apple.main-thread[0x100059ac0]>。

2016-02-19 14:31:23.391 gcd[96865:820267] 3.当前线程是: <NSThread: 0x1001053e0>{number = 1, name = main},当前队列是: <OS_dispatch_queue: com.apple.main-thread[0x100059ac0]>。

2016-02-19 14:31:24.396 gcd[96865:820267] 4.当前线程是: <NSThread: 0x1001053e0>{number = 1, name = main},当前队列是: <OS_dispatch_queue: queueKey1[0x103000000]>。

2016-02-19 14:31:25.400 gcd[96865:820267] 5.当前线程是: <NSThread: 0x1001053e0>{number = 1, name = main},当前队列是: <OS_dispatch_queue: queueKey1[0x103000000]>。

2016-02-19 14:31:26.402 gcd[96865:820367] 7. t当前线程是: <NSThread: 0x100105e10>{number = 2, name = (null)},当前队列是: <OS_dispatch_queue: queueKey1[0x103000000]>。

Program ended with exit code: 0
任务
同步

在当前线程中执行任务,不具备开启新线程的能力。

注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

demo:

无论怎样都不会开启新线程

/*
同步+并发 不会开启新线程,依旧在当前线程执行任务
*/
- (void)test3 {
dispatch_queue_t queue = dispatch_queue_create("syncConcrrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"1 %@", [NSThread currentThread]);
});

/*
2019-12-20 16:22:30.890416+0800 threadDemo[30526:6608103] 1 <NSThread: 0x600001b9c680>{number = 1, name = main}
*/
}

线程死锁

- (void)test1 {
// dispatch_async 开启的新线程,在队列 queue 中 ,往queue队列添加同步任务执行,线程卡死
dispatch_queue_t queue = dispatch_queue_create("asyncSerial", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{
NSLog(@"test1 start%@", [NSThread currentThread]);
//Thread 3: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync(queue, ^{
NSLog(@"test1 %@", [NSThread currentThread]);
});

NSLog(@"test1 end%@", [NSThread currentThread]);
});

/*
2019-12-20 09:56:54.932893+0800 threadDemo[11409:6405262] test1 start<NSThread: 0x600002235e40>{number = 5, name = (null)}

只打印了第一条。后面出现线程死锁了。

卡住原因:
1、串行队列,任务需要一个任务执行完毕接着下一个才执行
2、现在队列queue要执行 dispatch_sync 函数添加一个同步任务block
3、dispatch_sync 是同步的,需要将添加的任务block立即执行
4、此时调用 dispatch_sync 函数所在线程处于等待状态,需要block任务执行才继续往后执行,而执行 dispatch_sync 函数的线程与block执行所在线程是同一个线程,所以这个线程一直处于等待状态。不会往后执行,也不会执行block。
*/
}

```

YYKit库中应用

/**
YYKit库中,在SDWebImage中也有类似的应用

用于确保任务在主线程下执行
/
static inline void _yy_dispatch_sync_on_main_queue(void (^block)(void)) {
if (pthread_main_np()) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}


AFNetworking库中应用

/*

AFNetworking库中

AFURLSessionManager 中session创建时用到:
由于session创建在iOS8之前是线程不安全的,所以使用同步+串行队列实现锁的功能

注意:当前队列与同步函数中任务的队列不是一个队列
*/
// static void url_session_manager_create_task_safely(dispatch_block_t block) {
// if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// // Fix of bug
// // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
// dispatch_sync(url_session_manager_creation_queue(), block);
// } else {
// block();
// }
// }


###### 异步

在新的线程中执行任务,具备开启新线程的能力

demo:

在主队列,异步执行任务,不会开启新线程
  • (void)test1 {
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{ NSLog(@”1 %@”, [NSThread currentThread]); });
    dispatch_async(queue, ^{ NSLog(@”2 %@”, [NSThread currentThread]); });
    dispatch_async(queue, ^{ NSLog(@”3 %@”, [NSThread currentThread]); });

    /**

      2019-12-18 11:50:33.358149+0800 threadDemo[4613:5504490] 1 <NSThread: 0x600001cde1c0>{number = 1, name = main}
      2019-12-18 11:50:33.358549+0800 threadDemo[4613:5504490] 2 <NSThread: 0x600001cde1c0>{number = 1, name = main}
      2019-12-18 11:50:33.358696+0800 threadDemo[4613:5504490] 3 <NSThread: 0x600001cde1c0>{number = 1, name = main}
    

    */
    }


除主队列外,异步执行任务,都会开启新线程

  • (void)test2 {
    dispatch_queue_t queue1= dispatch_queue_create(“asyncSerial”, DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue1, ^{ NSLog(@”1-1 %@”, [NSThread currentThread]); });
    dispatch_async(queue1, ^{ NSLog(@”1-2 %@”, [NSThread currentThread]); });
    dispatch_async(queue1, ^{ NSLog(@”1-3 %@”, [NSThread currentThread]); });

    /**

      2019-12-18 11:53:59.972777+0800 threadDemo[4656:5507191] 1 <NSThread: 0x6000012fd380>{number = 3, name = (null)}
      2019-12-18 11:53:59.973284+0800 threadDemo[4656:5507191] 2 <NSThread: 0x6000012fd380>{number = 3, name = (null)}
      2019-12-18 11:53:59.973437+0800 threadDemo[4656:5507191] 3 <NSThread: 0x6000012fd380>{number = 3, name = (null)}
    

    */

// dispatch_queue_t queue2 = dispatch_queue_create(“asyncConcurrent”, DISPATCH_QUEUE_CONCURRENT);
// dispatch_async(queue2, ^{ NSLog(@”2-1 %@”, [NSThread currentThread]); });
// dispatch_async(queue2, ^{ NSLog(@”2-2 %@”, [NSThread currentThread]); });
// dispatch_async(queue2, ^{ NSLog(@”2-3 %@”, [NSThread currentThread]); });

/**
 2019-12-18 11:57:09.136050+0800 threadDemo[4686:5509014] 2-2 <NSThread: 0x600002350000>{number = 6, name = (null)}
 2019-12-18 11:57:09.136154+0800 threadDemo[4686:5509013] 2-1 <NSThread: 0x60000237bd00>{number = 5, name = (null)}
 2019-12-18 11:57:09.136174+0800 threadDemo[4686:5509015] 2-3 <NSThread: 0x60000237b480>{number = 4, name = (null)}
 */

}


##### Dispatch Group

[参考](https://xiaozhuanlan.com/topic/0863519247)

dispatch_group可以将GCD的任务合并到一个组里来管理,也可以同时监听组里所有任务的执行情况。

###### dispatch_group_create

本质是一个初始value为LONG_MAX的semaphore,通过信号量来实现一组任务的管理,代码如下:


dispatch_group_t dispatch_group_create(void) {
//申请内存空间
dispatch_group_t dg = (dispatch_group_t)_dispatch_alloc(
DISPATCH_VTABLE(group), sizeof(struct dispatch_semaphore_s));
//使用LONG_MAX初始化信号量结构体
_dispatch_semaphore_init(LONG_MAX, dg);
return dg;
}

###### dispatch_group_async

加入组的任务异步执行

###### dispatch_group_enter / leave

dispatch_group_enter的逻辑是将dispatch_group_t转换成dispatch_semaphore_t后将dsema_value的值减一。

应用注意:
1、dispatch_group_enter必须在dispatch_group_leave之前出现
2、dispatch_group_enter和dispatch_group_leave必须成对出现

demo:

  • (void)test1 {
    NSLog(@”1:%@”,[NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create(“concurrentQueue”, DISPATCH_QUEUE_CONCURRENT);

// //注意不要放在这,而应该放在dispatch_group_enter / leave 最后面。
// //否则可额能计数不对,导致提前回调
// dispatch_group_notify(group, queue, ^{
// NSLog(@”2”);
// });

dispatch_group_enter(group);
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"3:%@",[NSThread currentThread]);
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"4:%@",[NSThread currentThread]);
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{
        NSLog(@"2:%@",[NSThread currentThread]);
});

NSLog(@"5:%@",[NSThread currentThread]);

/**
2019-12-18 16:03:42.575564+0800 threadDemo[5518:5610267] 1:<NSThread: 0x600001c085c0>{number = 1, name = main}
2019-12-18 16:03:42.576226+0800 threadDemo[5518:5610267] 5:<NSThread: 0x600001c085c0>{number = 1, name = main}
2019-12-18 16:03:43.578856+0800 threadDemo[5518:5610368] 3:<NSThread: 0x600001c4ffc0>{number = 4, name = (null)}
2019-12-18 16:03:45.579819+0800 threadDemo[5518:5610366] 4:<NSThread: 0x600001c4b580>{number = 6, name = (null)}
2019-12-18 16:03:45.580037+0800 threadDemo[5518:5610366] 2:<NSThread: 0x600001c4b580>{number = 6, name = (null)}
*/

}


###### dispatch_group_wait

同步等待,直到group里面的block全部执行完毕,才会继续往后执行。

需要注意下 dispatch_group_wait 的位置,不能放在任务添加之前。

demo:

  • (void)test3 {
    dispatch_queue_t aDQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    // Add a task to the group
    dispatch_group_async(group, aDQueue, ^{

      sleep(2);
      printf("task 1 \n");
    

    });
    dispatch_group_async(group, aDQueue, ^{

      printf("task 2 \n");
    

    });

    printf(“wait 1 2 \n”);
    //同步等待,直到group里面的block全部执行完毕,才会继续往后执行。
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    printf(“task 1 2 finished \n”);

    /**

      wait 1 2
      task 2
      task 1
      task 1 2 finished
    

    */
    }


    ###### dispatch_group_notify

    队列组拦截通知模块(内部本身是异步执行的,不会阻塞线程)

    需要注意下 dispatch_group_notify 的位置,不能放在任务添加之前。

    demo:

  • (void)test2 {
    NSLog(@”1:%@”,[NSThread currentThread]);
    // 创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();

    /*
    // 不要写在这,没意义,我们都知道他在组内所有的任务执行完毕会调用 dispatch_group_notify 中的回调块。
    // 但是后面还有一句,当组内没有任务时,dispatch_group_notify 中的回调块也会立即执行。
    // 队列组拦截通知模块(内部本身是异步执行的,不会阻塞线程)
    dispatch_group_notify(group, queue, ^{

      NSLog(@"4:%@",[NSThread currentThread]);
    

    });

    2019-12-18 16:07:54.677923+0800 threadDemo[5581:5613688] 1:{number = 1, name = main}
    2019-12-18 16:07:54.678160+0800 threadDemo[5581:5613816] 4:{number = 6, name = (null)}
    2019-12-18 16:07:55.678663+0800 threadDemo[5581:5613688] 5:{number = 1, name = main}
    2019-12-18 16:07:56.681079+0800 threadDemo[5581:5613812] 2:{number = 5, name = (null)}
    2019-12-18 16:07:57.680645+0800 threadDemo[5581:5613815] 3:{number = 3, name = (null)}
    */

    //队列组异步执行任务
    dispatch_group_async(group, queue, ^{

      sleep(2);
      NSLog(@"2:%@",[NSThread currentThread]);
    

    });
    dispatch_group_async(group, queue, ^{

      sleep(3);
      NSLog(@"3:%@",[NSThread currentThread]);
    

    });
    // 队列组拦截通知模块(内部本身是异步执行的,不会阻塞线程)
    dispatch_group_notify(group, queue, ^{

      NSLog(@"4:%@",[NSThread currentThread]);
    

    });

    sleep(1);
    NSLog(@”5:%@”,[NSThread currentThread]);

    /
    2019-12-18 16:06:55.380553+0800 threadDemo[5558:5612492] 1:{number = 1, name = main}
    2019-12-18 16:06:56.381177+0800 threadDemo[5558:5612492] 5:{number = 1, name = main}
    2019-12-18 16:06:57.384415+0800 threadDemo[5558:5612611] 2:{number = 5, name = (null)}
    2019-12-18 16:06:58.385496+0800 threadDemo[5558:5612614] 3:{number = 3, name = (null)}
    2019-12-18 16:06:58.385737+0800 threadDemo[5558:5612614] 4:{number = 3, name = (null)}
    /
    }


    ##### dispatch_block

    [dispatch_block](https://www.cnblogs.com/KobeLuo/p/6464233.html)

    [dispatch_block](https://www.jianshu.com/p/5a16dfd36fad)


    ##### dispatch_semaphore
    信号量,有三个方法:

    ###### dispatch_semaphore_create

    dispatch_semaphore_t dispatch_semaphore_create(long value);
    创建一个信号量,信号量的值为入参 value

    ###### dispatch_semaphore_wait

    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    接收一个信号和时间值,若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;
    若信号量大于0,则会使信号量减1并返回,程序继续住下执行

    ###### dispatch_semaphore_signal

    long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
    接收一个信号量,发送信号使信号量的值 +1并返回

    ###### 使用

    应用场景:

    1. 充当锁的功能
    2. 异步任务,同步返回(同步获取指定APP在AppStore中的当前版本)
    3. 并发控制(实现与NSOperationQueue中max-ConcurrentOperationCount 类似功能。)

    demo:

    并发控制

    @implementation SemaphoreMaxConcurrentCount {
    dispatch_semaphore_t _semaphore;
    dispatch_queue_t _queue;
    }

  • (instancetype)init {
    return [self initWithMaxConcurrentCount:3];;
    }

  • (instancetype)initWithMaxConcurrentCount:(NSInteger)count {
    if (self = [super init]) {

      if (count < 1) {
          count = 3;
      }
      _semaphore = dispatch_semaphore_create(count);
      _queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    

    }
    return self;
    }

  • (void)addTask:(TaskBlock)block {
    dispatch_async(_queue, ^{
      dispatch_semaphore_wait(self->_semaphore, DISPATCH_TIME_FOREVER);
      dispatch_async(dispatch_get_global_queue(0, 0), ^{
          block();
          dispatch_semaphore_signal(self->_semaphore);
      });
    
    });
    }

@end


AFNetworking库中


/**
AFNetworking库中

获取session中完成的tasks。这个 与 appVersionInAppStore: 方法中的作用差不多。

都是通过block获取数据,然后将数据直接返回出去,避免了其他地方获取task也要用回调的方式获取数据。

/
//- (NSArray
)tasksForKeyPath:(NSString )keyPath {
// __block NSArray
tasks = nil;
// dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// [self.session getTasksWithCompletionHandler:^(NSArray dataTasks, NSArray uploadTasks, NSArray *downloadTasks) {
// if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
// tasks = dataTasks;
// } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
// tasks = uploadTasks;
// } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
// tasks = downloadTasks;
// } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
// tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@”@unionOfArrays.self”];
// }
//
// dispatch_semaphore_signal(semaphore);
// }];
//
// dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//
// return tasks;
//}


充当锁的功能

/**
充当锁的功能

当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;
当在线程1NSLog这一行代码还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完NSLog并执行完dispatch_semaphore_signal使信号量为1后,线程2才能解除阻塞继续住下执行。
这就可以保证同时只有一个线程执行NSLog这一行代码。

*/

  • (void)test1 {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    for (int i = 0; i < 100; i++) {

      dispatch_async(queue, ^{
          // 相当于加锁
          dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
          NSLog(@"i = %d semaphore = %@", i, semaphore);
          // 相当于解锁
          dispatch_semaphore_signal(semaphore);
      });
    

    }
    }


    异步任务,同步返回(同步获取指定APP在AppStore中的当前版本)

    /**
    异步任务,同步返回

    同步获取指定APP在AppStore中的当前版本
    */

  • (NSString )appVersionInAppStore:(NSString )appId {
    __block NSString appVersion = @””;
    NSString
    url = [NSString stringWithFormat:@”https://itunes.apple.com/lookup?id=%@",appId];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    NSURLSessionDataTask dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData _Nullable data, NSURLResponse _Nullable response, NSError _Nullable error) {

      NSError *err;
      NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingMutableContainers) error:&err];
      if (!err) {
          NSArray *results = jsonData[@"results"];
          if ([results isKindOfClass:[NSArray class]] && results != nil && results.count > 0) {
              appVersion = results.firstObject[@"version"];
          }
      }
    
      dispatch_semaphore_signal(semaphore);
    

    }];

    [dataTask resume];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return appVersion;


    ##### dispatch_barrier

    在一个并行队列中,有多个线程在执行多个任务,在这个并行队列中,有一个dispatch_barrier任务。这样会使所有在这个dispatch_barrier之后的任务总会等待barrier之前的所有任务结束之后,才会执行。

    dispatch_barrier 又分为 dispatch_barrier_sync dispatch_barrier_async

    注意:
    1.barrier和串行队列配合是完全没有意义的。
    barrier的目的是为了在某种情况下,同一个队列中一些并发任务必须在另一些并发任务之后执行,所以需要一个类似于拦截的功能,迫使后执”
    “行的任务必须等待。那么,串行队列中的所有任务本身就是按照顺序执行的。

    2.在global queue中使用barrier没有意义。
    barrier实现的基本条件是,要写在同一队列中。举个例子,你现在创建了两个并行队列,你在其中一个队列中插入了一个barrier任务,那么你不可能期待他可以在第二个队列中生效,对吧。同样的,每一次使用global queue,系统分配给你的可能是不同的并行队列,你在其中插入一个barrier任务,没有意义。

    ###### dispatch_barrier_sync

    dispatch_barrier_sync 中的任务同步执行,会阻塞当前线程

    demo:

  • (void)test1 {
    dispatch_queue_t queue = dispatch_queue_create(“asyncConcurrent”, DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 10; i++) {

      if (i % 2 == 0) {
          dispatch_async(queue, ^{
              sleep(1);
              NSLog(@"dispatch_barrier_sync 之前的任务:%d",i);
          });
      }
      else {
          dispatch_async(queue, ^{
              NSLog(@"dispatch_barrier_sync 之前的任务:%d",i);
          });
      }
    

    }

    dispatch_barrier_sync(queue, ^{

      NSLog(@"dispatch_barrier_sync 任务执行 %@", [NSThread currentThread]);
    

    });

    NSLog(@”dispatch_barrier_sync 所在线程 %@”, [NSThread currentThread]);

    for (int i = 0; i < 10; i++) {

      if (i % 2 == 0) {
          dispatch_async(queue, ^{
              sleep(1);
              NSLog(@"dispatch_barrier_sync 之后的任务:%d",i);
          });
      }
      else {
          dispatch_async(queue, ^{
              NSLog(@"dispatch_barrier_sync 之后的任务:%d",i);
          });
      }
    

    }

    /*
    2019-12-19 13:41:20.335598+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之前的任务:1
    2019-12-19 13:41:20.335702+0800 threadDemo[8655:6044001] dispatch_barrier_sync 之前的任务:3
    2019-12-19 13:41:20.336030+0800 threadDemo[8655:6044003] dispatch_barrier_sync 之前的任务:5
    2019-12-19 13:41:20.336205+0800 threadDemo[8655:6044004] dispatch_barrier_sync 之前的任务:7
    2019-12-19 13:41:20.336373+0800 threadDemo[8655:6044005] dispatch_barrier_sync 之前的任务:9
    2019-12-19 13:41:21.340593+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之前的任务:6
    2019-12-19 13:41:21.340593+0800 threadDemo[8655:6043998] dispatch_barrier_sync 之前的任务:0
    2019-12-19 13:41:21.340593+0800 threadDemo[8655:6044001] dispatch_barrier_sync 之前的任务:8
    2019-12-19 13:41:21.340593+0800 threadDemo[8655:6044002] dispatch_barrier_sync 之前的任务:4
    2019-12-19 13:41:21.340636+0800 threadDemo[8655:6044000] dispatch_barrier_sync 之前的任务:2
    2019-12-19 13:41:21.340931+0800 threadDemo[8655:6043503] dispatch_barrier_sync 任务执行 {number = 1, name = main}
    2019-12-19 13:41:21.341159+0800 threadDemo[8655:6043503] dispatch_barrier_sync 所在线程 {number = 1, name = main}
    2019-12-19 13:41:21.341339+0800 threadDemo[8655:6044002] dispatch_barrier_sync 之后的任务:1
    2019-12-19 13:41:21.341552+0800 threadDemo[8655:6043998] dispatch_barrier_sync 之后的任务:3
    2019-12-19 13:41:21.342288+0800 threadDemo[8655:6044002] dispatch_barrier_sync 之后的任务:5
    2019-12-19 13:41:21.342807+0800 threadDemo[8655:6044004] dispatch_barrier_sync 之后的任务:7
    2019-12-19 13:41:21.342890+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之后的任务:9
    2019-12-19 13:41:22.345189+0800 threadDemo[8655:6044001] dispatch_barrier_sync 之后的任务:2
    2019-12-19 13:41:22.345189+0800 threadDemo[8655:6044005] dispatch_barrier_sync 之后的任务:4
    2019-12-19 13:41:22.345227+0800 threadDemo[8655:6044003] dispatch_barrier_sync 之后的任务:6
    2019-12-19 13:41:22.345227+0800 threadDemo[8655:6044000] dispatch_barrier_sync 之后的任务:0
    2019-12-19 13:41:22.345237+0800 threadDemo[8655:6043998] dispatch_barrier_sync 之后的任务:8
    /
    }


    ###### dispatch_barrier_async

    dispatch_barrier_async 中的任务异步执行,不会阻塞当前线程

    demo:

    ```
    - (void)test2 {
    dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
    dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"dispatch_barrier_sync 之前的任务:%d",i);
    });
    }
    else {
    dispatch_async(queue, ^{
    NSLog(@"dispatch_barrier_sync 之前的任务:%d",i);
    });
    }
    }

    dispatch_barrier_async(queue, ^{
    NSLog(@"dispatch_barrier_sync 任务执行 %@", [NSThread currentThread]);
    });

    NSLog(@"dispatch_barrier_async 所在线程 %@", [NSThread currentThread]);

    for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
    dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"dispatch_barrier_sync 之后的任务:%d",i);
    });
    }
    else {
    dispatch_async(queue, ^{
    NSLog(@"dispatch_barrier_sync 之后的任务:%d",i);
    });
    }
    }

    /**
    2019-12-19 13:40:48.135837+0800 threadDemo[8655:6043503] dispatch_barrier_async 所在线程 <NSThread: 0x6000014820c0>{number = 1, name = main}
    2019-12-19 13:40:48.137458+0800 threadDemo[8655:6043615] dispatch_barrier_sync 之前的任务:1
    2019-12-19 13:40:48.152424+0800 threadDemo[8655:6043615] dispatch_barrier_sync 之前的任务:3
    2019-12-19 13:40:48.152613+0800 threadDemo[8655:6043615] dispatch_barrier_sync 之前的任务:5
    2019-12-19 13:40:48.152732+0800 threadDemo[8655:6043711] dispatch_barrier_sync 之前的任务:7
    2019-12-19 13:40:48.152829+0800 threadDemo[8655:6043615] dispatch_barrier_sync 之前的任务:9
    2019-12-19 13:40:49.140811+0800 threadDemo[8655:6043611] dispatch_barrier_sync 之前的任务:0
    2019-12-19 13:40:49.153305+0800 threadDemo[8655:6043710] dispatch_barrier_sync 之前的任务:6
    2019-12-19 13:40:49.153305+0800 threadDemo[8655:6043609] dispatch_barrier_sync 之前的任务:2
    2019-12-19 13:40:49.153305+0800 threadDemo[8655:6043709] dispatch_barrier_sync 之前的任务:4
    2019-12-19 13:40:49.153359+0800 threadDemo[8655:6043712] dispatch_barrier_sync 之前的任务:8
    2019-12-19 13:40:49.153667+0800 threadDemo[8655:6043712] dispatch_barrier_sync 任务执行 <NSThread: 0x60000140dd00>{number = 7, name = (null)}
    2019-12-19 13:40:49.153994+0800 threadDemo[8655:6043712] dispatch_barrier_sync 之后的任务:1
    2019-12-19 13:40:49.154061+0800 threadDemo[8655:6043710] dispatch_barrier_sync 之后的任务:3
    2019-12-19 13:40:49.154194+0800 threadDemo[8655:6043710] dispatch_barrier_sync 之后的任务:5
    2019-12-19 13:40:49.154419+0800 threadDemo[8655:6043714] dispatch_barrier_sync 之后的任务:7
    2019-12-19 13:40:49.154460+0800 threadDemo[8655:6043713] dispatch_barrier_sync 之后的任务:9
    2019-12-19 13:40:50.154831+0800 threadDemo[8655:6043709] dispatch_barrier_sync 之后的任务:0
    2019-12-19 13:40:50.154831+0800 threadDemo[8655:6043611] dispatch_barrier_sync 之后的任务:4
    2019-12-19 13:40:50.154869+0800 threadDemo[8655:6043712] dispatch_barrier_sync 之后的任务:6
    2019-12-19 13:40:50.154869+0800 threadDemo[8655:6043615] dispatch_barrier_sync 之后的任务:8
    2019-12-19 13:40:50.154867+0800 threadDemo[8655:6043609] dispatch_barrier_sync 之后的任务:2

    */
    }

dispatch_once

dispatch_once能保证任务只会被执行一次,同时多线程调用也是线程安全的。

原理:dispatch_once用原子性操作block执行完成标记位,同时用信号量确保只有一个线程执行block,等block执行完再唤醒所有等待中的线程。

应用场景:

  1. dispatch_once 常被用于创建单例
  2. 只需执行一次的函数都可以使用

demo:

AFNetworking 库中

/*
AFNetworking 库中创建队列
1、用到时才创建
2、确保只要创建一次
3、需要线程安全
综上原因,使用 dispatch_once 是最优选择

*/
//static dispatch_queue_t url_session_manager_processing_queue() {
// static dispatch_queue_t af_url_session_manager_processing_queue;
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
// });
//
// return af_url_session_manager_processing_queue;
//}

单利

- (void)test1 {
TDManager *manager1 = [TDManager shareManager];
TDManager *manager2 = [TDManager new];
TDManager *manager3 = [[TDManager alloc] init];
TDManager *manager4 = [manager1 copy];
TDManager *manager5 = [manager1 mutableCopy];

NSLog(@"manager1:%p",manager1);
NSLog(@"manager2:%p",manager2);
NSLog(@"manager3:%p",manager3);
NSLog(@"manager4:%p",manager4);
NSLog(@"manager5:%p",manager5);

/*
打印内存地址都一致,起到了单利效果

2019-12-20 17:00:15.974273+0800 threadDemo[30701:6628246] manager1:0x6000021d68b0
2019-12-20 17:00:15.974428+0800 threadDemo[30701:6628246] manager2:0x6000021d68b0
2019-12-20 17:00:15.974544+0800 threadDemo[30701:6628246] manager3:0x6000021d68b0
2019-12-20 17:00:15.974634+0800 threadDemo[30701:6628246] manager4:0x6000021d68b0
2019-12-20 17:00:15.974714+0800 threadDemo[30701:6628246] manager5:0x6000021d68b0
*/
}

dispatch_apply

dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次.
如果队列是并发队列,则会并发执行block任务;
如果队列是串行队列,则会串行在当前队列执行block任务;
dispatch_apply是一个同步调用,block任务执行n次后才返回。

demo:

串行队列中执行

/// 串行队列中执行,效率还是略高于普通的 for 循环
- (void)test1 {
NSLog(@"start");

dispatch_queue_t queue= dispatch_queue_create("asyncSerial", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);

CFTimeInterval startTimeInterval = CACurrentMediaTime();
//dispatch_apply是一个同步调用,block任务执行都执行完才返回,会卡住当前线程(无论串行还是并发队列)
dispatch_apply(10000, queue, ^(size_t i) {

// [NSThread sleepForTimeInterval:arc4random()%1];

NSLog(@"%zu %@",i,[NSThread currentThread]);
});
CFTimeInterval endTimeInterval = CACurrentMediaTime();
NSLog(@"end");

NSLog(@"endTimeInterval - startTimeInterval:%f",endTimeInterval - startTimeInterval);

/*
2019-12-19 15:46:19.281002+0800 threadDemo[9336:6115616] 0 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
2019-12-19 15:46:19.281440+0800 threadDemo[9336:6115616] 1 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
2019-12-19 15:46:19.281440+0800 threadDemo[9336:6115616] 2 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
2019-12-19 15:46:19.281440+0800 threadDemo[9336:6115616] 3 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
....
2019-12-19 15:46:19.281002+0800 threadDemo[9336:6115616] 9997 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
2019-12-19 15:46:19.281440+0800 threadDemo[9336:6115616] 9998 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
2019-12-19 15:46:19.282302+0800 threadDemo[9336:6115616] 9999 <NSThread: 0x6000008aa0c0>{number = 1, name = main}
2019-12-19 15:46:19.282819+0800 threadDemo[9336:6115616] end
2019-12-19 15:46:19.283203+0800 threadDemo[9336:6115616] endTimeInterval - startTimeInterval:4.206515

*/
}

并发队列执行

/// 并发队列中执行,比串行队列效率高
- (void)test2 {
NSLog(@"start");

dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);

CFTimeInterval startTimeInterval = CACurrentMediaTime();
//dispatch_apply是一个同步调用,block任务执行都执行完才返回,会卡住当前线程(无论串行还是并发队列)
dispatch_apply(10000, queue, ^(size_t i) {

// [NSThread sleepForTimeInterval:arc4random()%1];

NSLog(@"%zu %@",i,[NSThread currentThread]);
});
CFTimeInterval endTimeInterval = CACurrentMediaTime();
NSLog(@"end");

NSLog(@"endTimeInterval - startTimeInterval:%f",endTimeInterval - startTimeInterval);

/*

2019-12-19 16:23:13.700098+0800 threadDemo[9717:6141471] 0 <NSThread: 0x600002f5a140>{number = 1, name = main}
2019-12-19 16:23:13.700240+0800 threadDemo[9717:6141471] 1 <NSThread: 0x600002f5a140>{number = 1, name = main}
2019-12-19 16:23:13.700767+0800 threadDemo[9717:6141471] 2 <NSThread: 0x600002f5a140>{number = 1, name = main}
...
2019-12-19 16:23:17.318679+0800 threadDemo[9717:6141471] 9995 <NSThread: 0x600002f5a140>{number = 1, name = main}
2019-12-19 16:23:17.318908+0800 threadDemo[9717:6141715] 9996 <NSThread: 0x600002fd6900>{number = 8, name = (null)}
2019-12-19 16:23:17.319090+0800 threadDemo[9717:6141471] 9997 <NSThread: 0x600002f5a140>{number = 1, name = main}
2019-12-19 16:23:17.319294+0800 threadDemo[9717:6141714] 9998 <NSThread: 0x600002fd6780>{number = 7, name = (null)}
2019-12-19 16:23:17.319498+0800 threadDemo[9717:6141662] 9999 <NSThread: 0x600002fd6740>{number = 6, name = (null)}
2019-12-19 16:23:17.321036+0800 threadDemo[9717:6141471] end
2019-12-19 16:23:17.321507+0800 threadDemo[9717:6141471] endTimeInterval - startTimeInterval:3.620898
*/
}
dispatch_after

延时函数,不会卡住所在线程

demo:

任务执行所在队列为主队列,任务在主线程中执行的

- (void)test1 {
NSLog(@"1 %@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2 %@",[NSThread currentThread]);
});
NSLog(@"3 %@",[NSThread currentThread]);

/*
2019-12-19 14:58:46.529442+0800 threadDemo[8900:6083577] 1 <NSThread: 0x6000036b8b80>{number = 1, name = main}
2019-12-19 14:58:46.529659+0800 threadDemo[8900:6083577] 3 <NSThread: 0x6000036b8b80>{number = 1, name = main}
2019-12-19 14:58:48.529967+0800 threadDemo[8900:6083577] 2 <NSThread: 0x6000036b8b80>{number = 1, name = main}

*/
}

修改任务执行所在队列为并发队列. 任务是在子线程中执行的.

- (void)test2 {
NSLog(@"1 %@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
NSLog(@"2 %@",[NSThread currentThread]);
});
NSLog(@"3 %@",[NSThread currentThread]);

/*
2019-12-19 15:00:48.693679+0800 threadDemo[8943:6085531] 1 <NSThread: 0x600000d72ac0>{number = 1, name = main}
2019-12-19 15:00:48.694252+0800 threadDemo[8943:6085531] 3 <NSThread: 0x600000d72ac0>{number = 1, name = main}
2019-12-19 15:00:50.855673+0800 threadDemo[8943:6085641] 2 <NSThread: 0x600000d21080>{number = 3, name = (null)}

*/
}

修改任务执行所在队列为自定义的串行队列. 任务是在子线程中执行的.
通过查看打印结果,很明显延时函数底层肯定是异步执行任务。只有在主队列时任务才在主线程执行

- (void)test3 {
NSLog(@"1 %@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("syncConcrrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
});
NSLog(@"3 %@",[NSThread currentThread]);

/*
2019-12-19 15:09:06.207532+0800 threadDemo[9037:6090879] 1 <NSThread: 0x600001960f40>{number = 1, name = main}
2019-12-19 15:09:06.207745+0800 threadDemo[9037:6090879] 3 <NSThread: 0x600001960f40>{number = 1, name = main}
2019-12-19 15:09:08.207879+0800 threadDemo[9037:6090962] 2 <NSThread: 0x6000019ee4c0>{number = 8, name = (null)}
*/
}
Dispatch Source

Dispatch Source是BSD系统内核惯有功能kqueue的包装,kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。
它的CPU负荷非常小,尽量不占用资源。当事件发生时,Dispatch Source会在指定的Dispatch Queue中执行事件的处理。

dispatch_source_t
一共有一下几种类型(dispatch_source_type_t):

监控进程:DISPATCH_SOURCE_TYPE_PROC,
定时器:DISPATCH_SOURCE_TYPE_TIMER,
从描述符中读取数据:DISPATCH_SOURCE_TYPE_READ,
向描述符中写入字符:DISPATCH_SOURCE_TYPE_WRITE,
监控文件系统对象:DISPATCH_SOURCE_TYPE_VNODE,…..

demo演示定时器使用

Dispatch Source使用最多的就是用来实现定时器,source创建后默认是暂停状态,需要手动调用dispatch_resume启动定时器。dispatch_after只是封装调用了dispatch source定时器,然后在回调函数中执行定义的block。

Dispatch Source定时器使用时也有一些需要注意的地方,不然很可能会引起crash:

1、循环引用:因为dispatch_source_set_event_handler回调是个block,在添加到source的链表上时会执行copy并被source强引用,如果block里持有了self,self又持有了source的话,就会引起循环引用。正确的方法是使用weak+strong或者提前调用dispatch_source_cancel取消timer。

2、dispatch_resume和dispatch_suspend调用次数需要平衡,如果重复调用dispatch_resume则会崩溃,因为重复调用会让dispatch_resume代码里if分支不成立,从而执行了DISPATCH_CLIENT_CRASH(“Over-resume of an object”)导致崩溃。

3、source在suspend状态下,如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在resume状态下调用dispatch_source_cancel(source)后再重新创建。

GCD定时器

定时器不受runloop影响,效率高。

demo:

- (void)test1 {
__block int timeout=30; //倒计时时间
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), 1 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
if(timeout<=0){ //倒计时结束,关闭
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
//设置界面的按钮显示 根据自己需求设置
NSLog(@"time 倒计时结束");
});
}else{
int minutes = timeout / 60;
int seconds = timeout % 60;
NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒后重新获取验证码",minutes, seconds];
dispatch_async(dispatch_get_main_queue(), ^{
//设置界面的按钮显示 根据自己需求设置
NSLog(@"strTime %@",strTime);
});
timeout--;
}
});
//启动timer
dispatch_resume(_timer);
}

线程安全

只有当多个线程同时去访问并且修改一块资源的内容时可能会有线程安全问题。

当多线程只是访问并不做修改,不会出现线程安全问题。

举例:

假设银行卡有余额1000元,有2个线程A、B;
现在A、B同时查询余额,得到的都是1000元;
然后A线程存入1000元,余额为1000 + 1000 = 2000;
然后B线程取出500元,余额为1000 - 500 = 500;
这样就出现线程安全问题了,下次去查询余额就只有500了😹

为了保证线程安全,需要给线程加锁。

同步方案
OSSpinLock

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

目前已经不再安全,可能会出现优先级反转问题

如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁

需要导入头文件#import

os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持

从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

需要导入头文件#import

6a32356cd62bffb395c55eff0729375d.png

pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态

需要导入头文件#import

9848ab391ea130b47de60ac90949c5d7.png
1e9bf9133cafc7fe1e68e53239f70754.png
8d423ed41c44ae90fe1f4e55b26964f9.png
eeeb5c6a5d872001eb1f1c7917d810e0.png

dispatch_semaphore

semaphore叫做”信号量”

信号量的初始值,可以用来控制线程并发访问的最大数量

信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

844e0aa7a21c56f9568325ebf1df04b2.png

dispatch_queue(DISPATCH_QUEUE_SERIAL)

直接使用GCD的串行队列,也是可以实现线程同步的

4da7c092e947d453289322166866701b.png

NSLock、NSRecursiveLock

NSLock是对mutex普通锁的封装

1cb32ab8ac4967086fbbee75dbb15433.png
f288c06d4f718baddb110b329023936d.png
c02fbd60aa09ed858fec23c3b8d4a6a3.png

NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致

NSCondition

NSCondition是对mutex和cond的封装

5f93ec4282fecd19e347dd383be722ec.png

NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

65483fd9373eed4ce11ef7ecb2b60fda.png

@synchronized

@synchronized是对mutex递归锁的封装

源码查看:objc4中的objc-sync.mm文件

@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

27d559c23065755f60c6afe842dd3096.png

同步方案性能比较

性能从高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized

自旋 / 互斥锁
什么是自旋锁

等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

什么是互斥锁

等待锁的线程会处于休眠状态

什么情况使用自旋锁更划算?

预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器

什么情况使用互斥锁更划算?

预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈

高性能读写安全方案
实现思路

同一时间,只能有1个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作

实现方案

“多读单写”,经常用于文件等数据的读写操作,实现方案有
pthread_rwlock:读写锁
dispatch_barrier_async:异步栅栏调用

demo:

由于 NSMutableDictionary 线程是不安全的。

现在实现一个线程安全的可变字典

typedef void (^ThreadSafeBlock)(ThreadSafeMutableDictionary *dict, NSString *key, id object);

@implementation ThreadSafeMutableDictionary {
dispatch_queue_t _concurrentQueue;
}

- (instancetype)init {
if (self = [super init]) {
_concurrentQueue = dispatch_queue_create(@"com.thread.ThreadSafeMutableDictionary", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}

- (void)objectForKey:(NSString *)key block:(ThreadSafeBlock)block {
id cKey = [key copy];
__weak __typeof__(self) weakSelf = self;

dispatch_sync(_concurrentQueue, ^{
ThreadSafeMutableDictionary *strongSelf = weakSelf;
if (!strongSelf) {
block(nil,cKey,nil);
return ;
}

id object = [strongSelf objectForKey:cKey];
block(strongSelf,cKey,object);
});
}

- (void)setObject:(id)object forKey:(NSString *)key block:(ThreadSafeBlock)block {
if (!key || !object) {
return;
}

NSString *aKey = [key copy];
__weak __typeof__(self) weakSelf = self;
dispatch_barrier_async(_concurrentQueue, ^{
ThreadSafeMutableDictionary *strongSelf = weakSelf;
if (!strongSelf) {
block(nil,aKey,nil);
return ;
}

[self setObject:object forKey:aKey];
if (block) {
block(strongSelf,aKey,object);
}
});
}

多线程优化

优化思路
尽量减少队列切换

当线程数量超过 CPU 核心数量,CPU 核心通过线程调度切换用户态线程,意味着有上下文的转换,过多的上下文切换会带来资源开销。

如下代码:

dispatch_queue_t queue = dispatch_queue_create("x.x.x", DISPATCH_QUEUE_CONCURRENT);
- (void)tast1 {
dispatch_async(queue, ^{
//执行任务1
dispatch_async(dispatch_get_main_queue(), ^{
//任务1完成
[self tast2];
});
});
}
- (void)tast2 {
dispatch_async(queue, ^{
//执行任务2
dispatch_async(dispatch_get_main_queue(), ^{
//任务2完成
});
});
}

这里创建了一个并行队列,调用 tast1 会执行两个任务,任务2要等待任务1执行完成,这里一共有四次队列的切换。其实是没必要的。

优化后:

dispatch_queue_t queue = dispatch_queue_create("x.x.x", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//执行任务1
//执行任务2
dispatch_async(dispatch_get_main_queue(), ^{
//任务1、2完成
});
});
控制线程数量

使用 GCD 并行队列,当任务过多且耗时较长时,队列会开辟大量的线程,而部分线程里面的耗时任务已经耗尽了 CPU 资源,所以其他的线程也只能等待 CPU 时间片,过多的线程也会让线程调度过于频繁。

GCD 中并行队列并不能限制线程数量,可以创建多个串行队列来模拟并行的效果,业界知名框架 YYKit 就做了这个逻辑,通过和 CPU 核心数量相同的串行队列轮询返回来达到并行队列的效果

static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
//最大队列数量
#define MAX_QUEUE_COUNT 16
//队列数量
static int queueCount;
//使用栈区的数组存储队列
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = 0;
dispatch_once(&onceToken, ^{
//串行队列数量和处理器数量相同
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
//创建串行队列,设置优先级
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
}
} else {
for (NSUInteger i = 0; i < queueCount; i++) {
queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
}
}
});
//轮询返回队列
uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);
return queues[cur % queueCount];
#undef MAX_QUEUE_COUNT
}
线程优先级权衡

线程调度除了轮转法以外,还有优先级调度的方案,在线程调度时,高优先级的线程会更早的执行。有两个概念需要明确:
a. IO 密集型线程:频繁等待的线程,等待的时候会让出时间片。
b. CPU 密集型线程:很少等待的线程,意味着长时间占用着 CPU。

这样就会存在一个问题:
当CPU密集型线程优先级较高,而且长期霸占CPU大部分资源,这样IO密集型线程由于优先级较低,就持续的等待,产生线程饿死的现象。这时系统会根据情况提高IO密集型线程的优先级,但即使这样,等待也是需要时间的。

这样可以考虑优化的方向:
a. 让 IO 密集型线程优先级高于 CPU 密集型线程。
b. 让紧急的任务拥有更高的优先级。

主线程任务的优化

这摘抄了yykit作者的提出的部分内容iOS 保持界面流畅的技巧

一些耗时的操作尽量移动到子线程执行。

但是有些操作必须在主线程,比如 UI 类组件的初始化及其布局。

那么主线程的优化有哪些方案可以参考呢?

1、尽量使用轻量级对象

对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗 CPU 资源。比如 CALayer 比 UIView 要轻量许多,那么不需要响应触摸事件的控件,用 CALayer 显示会更加合适。

2、尽量减少对象的调整

当视图层次调整时,UIView、CALayer 之间会出现很多方法调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。

3、对象销毁其实是可以在后台线程执行的

这时yykit作者提倡的方式:

需要注意的是下面的例子中,如果self.array在别的地方还有依赖,那tmp不是唯一的销毁helper。这一套就没用了。

NSArray *tmp = self.array;
self.array = nil;
dispatch_async(queue, ^{
[tmp class];
});

4、内存复用

最常见的就是cell复用,避免了大量cell对象的创建,节省内存的同时也节省了开辟内存所消耗的时间。

5、懒加载任务

懒加载对象,用到时再创建,可以减少没必要的内存开销。开辟内存是要时间的。勉强算是变相的吧任务进行了拆分,不必初始化时就创建一系列对象。

6、任务拆分排队执行

将大量的任务拆分开来,监听Runloop运行状态,当runloop将要休息的时候,让 Runloop 循环周期执行少量任务。

。。。

学习

GCD源码

GNUstep

不再安全的 OSSpinLock

iOS 保持界面流畅的技巧

iOS 如何高效的使用多线程

深入浅出 GCD

Demo

threadDemo

  • Post title:GCD研究
  • Post author:ChenghuiBai
  • Create time:2018-07-08 11:37:36
  • Post link:https://baichenghui.github.io/2018/07/08/GCD研究/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.