iOS多线程详解

一.进程和线程

计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
摘自<<进程与线程的一个简单解释-阮一峰>>

二.多线程

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

三.四种方案对比

图片来自http://www.cocoachina.com/ios/20170707/19769.html

1.pthread

 - (IBAction)create_pthread:(id)sender {
    //声名线程变量
    pthread_t thread;
    /*
     pthread_create方法介绍:
     参数一:线程对象的地址
     参数二:设定线程的属性(例如:子线程占用多少栈空间:512KB。。。一般设置为空NULL)
     参数三:子线程执行任务的函数声明
     参数四:传递给函数的参数
     返回值:0代表线程创建成功
     */
    int result = pthread_create(&thread, NULL, run,NULL);//创建线程
    if (!result) {
        NSLog(@"线程创建成功");
    }
}
//调用函数
void *run(void *param) {
    for (NSInteger i = 0; i < 10000; i ++) {
        NSLog(@"线程:%@正在打印++++++",[NSThread currentThread]);
    }
    return NULL;
}

2.NSThread

  • 方法一
- (IBAction)createThreadMethod1:(id)sender {
    //创建线程对象
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"执行任务1"];
    
    //设置名称
    thread1.name = @"子线程1";
    //设置线程优先级
    [thread1 setThreadPriority:0.8];
    //开启任务
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"执行任务2"];
    thread2.name = @"子线程2";
    [thread2 setThreadPriority:1.0];
    [thread2 start];
}
- (void)run:(NSString *)param {
    for (NSInteger i = 0; i < 100; i++) {
        NSLog(@"%@-%@",[NSThread currentThread],param);
    }
}
  • 方法二
- (IBAction)createThreadMethod2:(id)sender {
    //默认开始子线程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"子线程3"];
}
  • 方法三
- (IBAction)createThreadMethod3:(id)sender {
    //默认开启子线程
    [self performSelectorInBackground:@selector(run:) withObject:@"子线程4"];
}

3.GCD

(1)简介

全称Grand Central Dispatch,它是苹果为多核的并行运算提出的解决方案,会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是会自动管理线程的生命周期(创建线程、调度任务、销毁线程),只需要告诉GCD要执行什么任务,不需要编写任何管理代码。同时它使用的也是 c语言,不过由于使用了 Block,使得使用起来更加方便.

(2)基本概念

任务(block):任务就是将要在线程中执行的代码,将这段代码用block封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。
同步(sync):前一个任务没有执行完毕,后面的任务不能执行,不开子线程。
异步(async):前一个任务没有执行完毕,后面的任务可以执行。
dispatch_sync: dispatch_sync则是同步扔一个block到queue中,即扔了我就等着,等到queue排队把这个block执行完了之后,才继续执行下一行代码。
dispatch_async:异步扔一个block到queue中,即扔完我就不管了,继续执行我的下一行代码。实际上当下一行代码执行时,这个block还未执行,只是入了队列queue,queue会排队来执行这个block。
队列:装载线程任务的队形结构。(系统以先进先出的方式调度队列中的任务执行)。在GCD中有两种队列:串行队列和并发队列。
并发队列:线程可以同时一起进行执行。单核CPU的情况下,是系统在多条线程之间快速的切换。(并发功能只有在异步(dispatch_async)函数下才有效)
串行队列:线程只能依次有序的执行。
主队列:GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。
全局队列:GCD自带的一种特殊的并行队列.
GCD总结:将任务(要在线程中执行的操作block)添加到队列(自己创建或使用全局并发队列),并且指定执行任务的方式(异步dispatch_async,同步dispatch_sync)

(3)创建
    //创建一个串行队列【参数一:队列的唯一标识符,用于DEBUG,可为空。参数二:串行还是并行标识】
    dispatch_queue_t serialQueue = dispatch_queue_create("A Serial Queue", DISPATCH_QUEUE_SERIAL);
    //创建一个并行队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("A Concurrent Queue", DISPATCH_QUEUE_CONCURRENT);
    //获取主队列
    dispatch_queue_t systemSerialQueue = dispatch_get_main_queue();
    //获取全局队列【参数一:队列优先级 参数二:默认传0即可】
    dispatch_queue_t systemConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
(4)八种情况
  • 异步并行

队列中的任务并发执行,会创建一个或多个子线程。如果队列中的任务没有执行完毕,系统则会执行其他任务。

- (IBAction)concurrentQueueAsync:(id)sender {
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}

控制台输出:
<NSThread: 0x1c4072080>{number = 1, name = main}:打印+完毕
<NSThread: 0x1c4072080>{number = 1, name = main}:打印-完毕
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
  • 异步串行

队列中的任务依次执行,会创建一个或多个子线程。如果队列中的任务没有执行完毕,系统则会执行其他任务。

- (IBAction)serialQueueAsync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_async(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x1c0263300>{number = 1, name = main}:打印+完毕
<NSThread: 0x1c0263300>{number = 1, name = main}:打印-完毕
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
  • 同步并行

队列中的任务依次执行,不会创建子线程。只有队列中的任务执行完毕,系统才会执行下一个任务,会阻塞当前线程。

- (IBAction)concurrentQueueSync:(id)sender {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(concurrentQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_sync(concurrentQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:打印+完毕
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:打印-完毕
  • 同步串行

队列中的任务依次执行,不会创建子线程。只有队列中的任务执行完毕,系统才会执行下一个任务,会阻塞当前线程。

- (IBAction)serialQueueSync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_sync(serialQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:打印+完毕
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:打印-完毕
  • 主队列同步执行

主队列同步执行会造成死锁 原因在后面

    dispatch_sync(dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
  • 主队列异步执行

队列中的任务依次在主线程中执行,不会创建子线程。队列中的任务没有执行完毕,系统则会执行下一个任务,不会阻塞当前线程。

- (IBAction)mainQueueAsync:(id)sender {
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_async(mainQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x60000007b340>{number = 1, name = main}:打印+完毕
<NSThread: 0x60000007b340>{number = 1, name = main}:打印-完毕
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
  • 全局队列异步执行

队列中的任务并发执行,会创建一个或多个子线程。队列中的任务没有执行完毕,系统则会执行下一个任务,不会阻塞当前线程。

- (IBAction)globalQueueAsync:(id)sender {
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_async(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x60400007b7c0>{number = 1, name = main}:打印+完毕
<NSThread: 0x60400007b7c0>{number = 1, name = main}:打印-完毕
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
  • 全局队列同步执行

队列中的任务依次执行,不会创建子线程。队列中的任务没有执行完毕,系统不会执行下一个任务,会阻塞当前线程。

- (IBAction)globalQueueSync:(id)sender {
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_sync(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:----------",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印+完毕",[NSThread currentThread]);
    
    dispatch_sync(globalQueue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%@:++++++++++",[NSThread currentThread]);
        }
    });
    NSLog(@"%@:打印-完毕",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:打印+完毕
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:打印-完毕
(5)主队列同步执行造成死锁的原因
dispatch_sync(dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"%@:++++++++++",[NSThread currentThread]);
    }
});
同步:只有当前任务执行完毕,系统才会执行下一个任务。
主队列:Block中的任务和dispatch_sync函数都是需要在主队列中执行的。
Block中的任务需要主线程执行完dispatch_sync函数才会执行,dispatch_sync函数需要block执行完毕才会执行其他任务,两者相互等待,造成死锁。
  • 非主队列不会造成死锁的原因

dispatch_sync是在主队列com.apple.main-thread中执行的。
Block中的任务是在创建的serial.queue队列中执行的。
两者并不是在同一个队列中执行。Block的执行不需要等待dispatch_sync函数执行完毕才去执行。

- (IBAction)serialQueueSync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"当前队列:%@-%@:++++++++++",dispatch_get_current_queue(),[NSThread currentThread]);
    });
    NSLog(@"当前队列:%@-%@:打印完毕",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制台输出:
当前队列:<OS_dispatch_queue: serial.queue>-<NSThread: 0x6000000790c0>{number = 1, name = main}:++++++++++
当前队列:<OS_dispatch_queue_main: com.apple.main-thread>-<NSThread: 0x6000000790c0>{number = 1, name = main}:打印完毕
  • 在同一个队列同步执行两个任务是造成死锁的原因

在程序执行到第二个星号位置的dispatch_sync时,当前队列serial.queue需要执行dispatch_sync函数,但是dispatch_sync函数需要等待Block执行完毕才会执行,因为serial.queue是串行队列,其中的任务是一个一个执行的。Block任务排列在第二个星号位置的dispatch_sync函数的后面,Block中任务的执行需要等待dispatch_sync函数的执行才会执行,即造成了两者互相等待的情况,所以造成死锁。

- (IBAction)serialQueueSync:(id)sender {
    dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"打印+++++:%@-%@",dispatch_get_current_queue(),[NSThread currentThread]);
        *dispatch_sync(serialQueue, ^{
            NSLog(@"打印------:%@-%@",dispatch_get_current_queue(),[NSThread currentThread]);
        });
    });
}
(5)GCD线程通讯

当我们完成了耗时操作,需要回到主线程进行UI更新,这时就需要用到线程通讯:

dispatch_async(dispatch_queue_create("A Concurrent Queue", DISPATCH_QUEUE_CONCURRENT), ^{
    //耗时操作
    dispatch_async(dispatch_get_main_queue(), ^{
        //更新UI
    });
});

(6)GCD栅栏函数

  • 什么是栅栏函数
    1.GCD栅栏函数即dispatch_barrier_async函数,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行。
    2.当任务需要异步进行,但是这些任务需要分成两组来执行,第一组完成之后才能进行第二组的操作。这时候就用了到GCD的栅栏函数dispatch_barrier_async。
  • 栅栏函数的使用
- (IBAction)barrierMethod:(id)sender {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("a concurrent queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2:%@",[NSThread currentThread]);
    });
    dispatch_barrier_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"栅栏任务:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3:%@",[NSThread currentThread]);
    });
    
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4:%@",[NSThread currentThread]);
    });
}
控制台输出:
任务2:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
任务1:<NSThread: 0x600000274d40>{number = 3, name = (null)}
栅栏任务:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
任务4:<NSThread: 0x600000274d40>{number = 3, name = (null)}
任务3:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
上面的栅栏函数使用
dispatch_barrier_sync(concurrentQueue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"栅栏任务:%@",[NSThread currentThread]);
});
控制台输出为:
 任务2:<NSThread: 0x604000277180>{number = 3, name = (null)}
 任务1:<NSThread: 0x604000278a40>{number = 4, name = (null)}
 栅栏任务:<NSThread: 0x60400006b600>{number = 1, name = main}
 任务3:<NSThread: 0x604000277180>{number = 3, name = (null)}
 任务4:<NSThread: 0x60000046bb80>{number = 5, name = (null)}
在栅栏函数的block任务执行完毕以前,主线程都是阻塞状态。

(7)GCD延迟函数

  • 秒的单位换算
1秒=1000毫秒
1毫秒=1000微秒
1微秒=1000纳秒
1纳秒=1000皮秒
1皮秒=1000飞秒
  • 系统宏定义
#define NSEC_PER_SEC 1000000000ull //10亿纳秒等于1秒
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
#define DISPATCH_TIME_NOW (0ull)//代表从当前立即开始的时间
  • 延迟一段代码3秒以后执行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"在3秒钟以后输出这段文字");
});

(8)GCD一次性任务【可用于设计单例】

- (IBAction)onceTokenTask:(id)sender {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"在程序运行中,这段代码只会被执行一次");
    });
}

(8)dispatch_apply函数的使用

  • 函数作用
    把一项任务提交到队列中多次执行,具体是串行执行还是并行执行由队列本身决定,dispatch_apply不会立即返回,在执行完毕后才会返回,是同步的调用
- (IBAction)gcdApply:(id)sender {
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("aConcurrentQueue", DISPATCH_QUEUE_SERIAL);
    size_t size = 5;
    /*
     第一个参数:任务在队列中执行的次数
     第二个参数:任务执行的队列
     Block参数代表当前任务在队列中是第几次执行
     */
    dispatch_apply(size, concurrentQueue, ^(size_t index) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"当前打印:%zu:线程:%@",index,[NSThread currentThread]);
    });
    NSLog(@"执行其他任务:%@",[NSThread currentThread]);
}
控制台输出:
当前打印:0:线程:<NSThread: 0x604000261940>{number = 1, name = main}
当前打印:2:线程:<NSThread: 0x6000004696c0>{number = 3, name = (null)}
当前打印:3:线程:<NSThread: 0x60000027b2c0>{number = 4, name = (null)}
当前打印:1:线程:<NSThread: 0x6040006623c0>{number = 5, name = (null)}
当前打印:4:线程:<NSThread: 0x604000261940>{number = 1, name = main}
执行其他任务:<NSThread: 0x604000261940>{number = 1, name = main}

(8)队列组

  • 业务场景示例
    当我们在开发一个音乐APP时,需要下载很多首音乐,当下载完成以后需要通知用户所有音乐下载完成,这时我们就可以使用队列组实现这一场景。
  • 方法介绍
//dispatch_group_create()用户创建一个队列组
dispatch_group_t group = dispatch_group_create();
//用于添加对应队列组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行
dispatch_group_enter(group);
//用于减少队列组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter和dispatch_group_leave要匹配,即有加就要有减,不然系统会认为group任务没有执行完毕
dispatch_group_leave(group);
//延时执行当前任务,会阻塞当前线程
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC));
//在指定的队列组和队列中异步执行任务
dispatch_group_async(group, queue, ^{
});
//当队列组中所有任务执行完毕以后执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
  • 使用示例
- (IBAction)queueGroup:(id)sender {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@任务一",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:4.0f];
        NSLog(@"%@任务二",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任务全部完成了");
    });
    NSLog(@"额外任务:%@",[NSThread currentThread]);
}
控制台输出:
额外任务:<NSThread: 0x600000065000>{number = 1, name = main}
<NSThread: 0x60000026f4c0>{number = 3, name = (null)}任务一
<NSThread: 0x600000460440>{number = 4, name = (null)}任务二
任务全部完成了
  • 不能正确通知队列组中所有任务已经完成的情况
- (IBAction)queueGroup:(id)sender {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@任务一",[NSThread currentThread]);
        dispatch_async(queue, ^{
            [NSThread sleepForTimeInterval:8.0f];
            NSLog(@"%@任务三",[NSThread currentThread]);
        });
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:4.0f];
        NSLog(@"%@任务二",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任务全部完成了");
    });
    NSLog(@"额外任务:%@",[NSThread currentThread]);
}
控制台输出:
额外任务:<NSThread: 0x600000079440>{number = 1, name = main}
<NSThread: 0x60000046f7c0>{number = 3, name = (null)}任务一
<NSThread: 0x600000470ac0>{number = 4, name = (null)}任务二
任务全部完成了
<NSThread: 0x60000046f7c0>{number = 3, name = (null)}任务三

我们发现,在dispatch_group_notify函数调用以后,任务三才执行。很明显,这里并没有实现在所有任务执行完毕以后才去调用dispatch_group_notify函数。解决方案如下:

我们需要使用dispatch_group_enter和dispatch_group_leave函数
- (IBAction)queueGroup:(id)sender {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        //在每一次开始执行任务以前进行计数加
        dispatch_group_enter(group);
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@任务一",[NSThread currentThread]);
        dispatch_async(queue, ^{
            [NSThread sleepForTimeInterval:8.0f];
            NSLog(@"%@任务三",[NSThread currentThread]);
            //在每一次任务执行完毕以后进行计数减
            dispatch_group_leave(group);
        });
    });
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        [NSThread sleepForTimeInterval:4.0f];
        NSLog(@"%@任务二",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任务全部完成了");
    });
    NSLog(@"额外任务:%@",[NSThread currentThread]);
}
控制台输出如下:
额外任务:<NSThread: 0x600000073ec0>{number = 1, name = main}
<NSThread: 0x6000002792c0>{number = 3, name = (null)}任务一
<NSThread: 0x60000027a000>{number = 4, name = (null)}任务二
<NSThread: 0x6000002792c0>{number = 3, name = (null)}任务三
任务全部完成了

(9)GCD定时器

  • Dispatch Source
    Dispatch Source是GCD中的一种基本数据类型,从字面意思可称其为调度源,它用于处理特定的系统底层事件,即:当一些特定的系统底层事件发生时,调度源会捕捉到这些事件,然后可以做相应的逻辑处理。
  • Dispatch Source可处理的事件类型
    DISPATCH_SOURCE_TYPE_DATA_ADD 自定义的事件,变量增加
    DISPATCH_SOURCE_TYPE_DATA_OR 自定义的事件,变量OR
    DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
    DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
    DISPATCH_SOURCE_TYPE_PROC 进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
    DISPATCH_SOURCE_TYPE_READ IO操作,如对文件的操作、socket操作的读响应
    DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信号时响应
    DISPATCH_SOURCE_TYPE_TIMER 定时器
    DISPATCH_SOURCE_TYPE_VNODE 文件状态监听,文件被删除、移动、重命名
    DISPATCH_SOURCE_TYPE_WRITE IO操作,如对文件的操作、socket操作的写响应
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //创建 Timer Source
    dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 0);
    //设置 Timer Source 的启动时间、间隔时间、精度
    dispatch_source_set_timer(timerSource, time, 1.0*NSEC_PER_SEC, 0);
    //设置时间回调
    dispatch_source_set_event_handler(timerSource, ^{
        NSLog(@"当前时间:%@",[NSDate date]);
    });
    //开始执行 Dispatch Source
    dispatch_resume(timerSource);
    //强引用避免被提前释放
    self.timerSource = timerSource;

4.NSOperation

NSOperation是GCD的封装,完全面向对象。NSOperation是抽象类,需要使用它的子类NSInvocationOperation 和 NSBlockOperation,或者自定义子类也行。

(1)NSInvocationOperation的使用

  • 同步执行任务【会阻塞当前线程】
- (IBAction)syncTask:(id)sender {
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printTask) object:nil];
    [invocationOperation start];
    NSLog(@"%@:其他任务",[NSThread currentThread]);
}

- (void)printTask {
    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@-%@:执行打印任务",dispatch_get_current_queue(),[NSThread currentThread]);
}

控制台输出:
<OS_dispatch_queue_main: com.apple.main-thread>-<NSThread: 0x6000002638c0>{number = 1, name = main}:执行打印任务
<NSThread: 0x6000002638c0>{number = 1, name = main}:其他任务
  • 异步执行任务【不会阻塞当前线程】
- (IBAction)asyncTask:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printTask) object:nil];
    [queue addOperation:operation];
    NSLog(@"%@:其他任务",[NSThread currentThread]);
}
- (void)printTask {
    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@-%@:执行打印任务",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x60400006e440>{number = 1, name = main}:其他任务
<OS_dispatch_queue: NSOperationQueue 0x60000022d300 (QOS: UNSPECIFIED)>-<NSThread: 0x60400046d180>{number = 3, name = (null)}:执行打印任务

(2)NSBlockOperation的使用

  • 同步执行任务 并发 会阻塞当前线程
- (IBAction)nsblockOperationSync:(id)sender {
    NSBlockOperation *operation = [[NSBlockOperation alloc] init];
    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务一:%@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务二:%@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务三:%@",[NSThread currentThread]);
    }];
    [operation start];
    NSLog(@"%@其他任务",[NSThread currentThread]);
}
控制台输出:
任务三:<NSThread: 0x60000027d380>{number = 4, name = (null)}
任务一:<NSThread: 0x60400006fcc0>{number = 1, name = main}
任务二:<NSThread: 0x6000002778c0>{number = 3, name = (null)}
<NSThread: 0x60400006fcc0>{number = 1, name = main}其他任务
  • 异步执行任务 非主队列则为并发队列 不会阻塞当前线程 后添加的operation先执行
- (IBAction)nsblockOperationAsync:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务二:%@",[NSThread currentThread]);
    }];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    NSLog(@"%@其他任务",[NSThread currentThread]);
}
控制台输出:
<NSThread: 0x604000065f40>{number = 1, name = main}其他任务
任务二:<NSThread: 0x600000279540>{number = 3, name = (null)}
任务一:<NSThread: 0x604000263800>{number = 4, name = (null)}

(3)NSOperation操作依赖

  • 没有设置依赖关系
- (IBAction)setttingDependency:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"任务一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务二:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"任务三:%@",[NSThread currentThread]);
    }];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
    NSLog(@"%@其他任务",[NSThread currentThread]);
}
控制台输出如下:
<NSThread: 0x600000074d40>{number = 1, name = main}其他任务
任务三:<NSThread: 0x604000470340>{number = 3, name = (null)}
任务二:<NSThread: 0x60000046e980>{number = 4, name = (null)}
任务一:<NSThread: 0x60000046a2c0>{number = 5, name = (null)}
  • 设置了依赖关系
- (IBAction)setttingDependency:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3.0f];
        NSLog(@"任务一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务二:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"任务三:%@",[NSThread currentThread]);
    }];
    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
    NSLog(@"%@其他任务",[NSThread currentThread]);
}
控制台输出如下:
<NSThread: 0x600000261d00>{number = 1, name = main}其他任务
任务一:<NSThread: 0x60000047a380>{number = 3, name = (null)}
任务二:<NSThread: 0x60400027e680>{number = 4, name = (null)}
任务三:<NSThread: 0x60400027e680>{number = 4, name = (null)}

(4)NSOperation设置最大并发数

- (IBAction)settingMaxCount:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务一:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务二:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"任务三:%@",[NSThread currentThread]);
    }];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
}
控制台输出:
12:03:51 任务二:<NSThread: 0x600000261680>{number = 3, name = (null)}
12:03:51 任务一:<NSThread: 0x600000261700>{number = 4, name = (null)}
12:03:53 任务三:<NSThread: 0x604000474040>{number = 5, name = (null)}

(4)线程通信

- (IBAction)threadCommunication:(id)sender {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"耗时任务:%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"%@回主线程更新UI",[NSThread currentThread]);
        }];
    }];
    [queue addOperation:operation];
}
控制台输出:
耗时任务:<NSThread: 0x60000027e140>{number = 3, name = (null)}
<NSThread: 0x60400006b640>{number = 1, name = main}回主线程更新UI

四.线程安全

  • 线程不安全示例 卖票程序
    当我们运行下列程序:
- (IBAction)simultaneousAccess:(id)sender {
    //卖出了多少张票 初始为0张
    self.soldedTicketsCount = 0;
    //剩余多少票 初始为10000张
    self.totalTicketsCount = 10000;
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_threadUnsafety) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_threadUnsafety) object:nil];
    secondThread.name = @"窗口二";
    [firstThread start];
    [secondThread start];
}
- (void)sellTickets_threadUnsafety {
    while (1) {
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld卖出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
        } else {
            NSLog(@"票卖完了:剩余:%ld卖出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            return;
        }
    }
}
控制台输出:
票卖完了:剩余:0卖出了:9999
票卖完了:剩余:0卖出了:9999

可以明显看到,最后的输出结果是不正确的,因为在多个线程同时执行一个任务的时候,就会产生线程安全问题。ios中解决线程安全问题的办法有很多,下面介绍四种:@synchronized、NSLock、pthread_mutex_t、信号量

(1).@synchronized

  • 使用@synchronized加锁 可以保证同一时刻只有一个线程访问特定程序
  • @synchronized(nil)无效
  • @synchronized只对同一个对象有效
- (IBAction)synchronizedMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 10000;
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_synchronized) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_synchronized) object:nil];
    secondThread.name = @"窗口二";
    [firstThread start];
    [secondThread start];
}
- (void)sellTickets_synchronized {
    @synchronized(self) {
        while (1) {
            if (self.totalTicketsCount > 0) {
                self.totalTicketsCount --;
                self.soldedTicketsCount ++;
                NSLog(@"%@:剩余:%ld卖出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            } else {
                NSLog(@"票卖完了:剩余:%ld卖出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
                return;
            }
        }
    }
}
控制台最终输出:
 票卖完了:剩余:0卖出了:10000
 票卖完了:剩余:0卖出了:10000

(2).NSLock

  • NSLock 遵循 NSLocking 协议,lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。
- (IBAction)nslockMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 10000;
    //NSLock对象
    self.lock = [[NSLock alloc] init];
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_nslock) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_nslock) object:nil];
    secondThread.name = @"窗口二";
    [firstThread start];
    [secondThread start];
}
- (void)sellTickets_nslock {
    while (1) {
        //加锁
        [self.lock lock];
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld卖出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            //解锁
            [self.lock unlock];
        } else {
            NSLog(@"票卖完了:剩余:%ld卖出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            //解锁
            [self.lock unlock];
            return;
        }
    }
}
控制台输出:
票卖完了:剩余:0卖出了:10000
票卖完了:剩余:0卖出了:10000

(3).pthread_mutex_t

  • pthread_mutex_t是基于C语言的。
  • pthread_mutex_init用于创建并初始化pthread_mutex_t。
  • pthread_mutex_unlock解锁
  • pthread_mutex_lock加锁
//声名一个静态变量
static pthread_mutex_t mutex;
- (IBAction)pthread_mutex_tMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 100000;
    //创建并初始化
    pthread_mutex_init(&mutex, NULL);
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    secondThread.name = @"窗口二";
    NSThread *thirdThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    thirdThread.name = @"窗口三";
    NSThread *fourthThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
    fourthThread.name = @"窗口四";
    [firstThread start];
    [secondThread start];
    [thirdThread start];
    [fourthThread start];
}
- (void)sellTickets_pthread_mutex_t {
    while (1) {
        //加锁
        pthread_mutex_lock(&mutex);
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld卖出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            //解锁
            pthread_mutex_unlock(&mutex);
        } else {
            NSLog(@"票卖完了:剩余:%ld卖出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            //解锁
            pthread_mutex_unlock(&mutex);
            return;
        }
    }
}
控制台输出:
票卖完了:剩余:0卖出了:100000
票卖完了:剩余:0卖出了:100000
票卖完了:剩余:0卖出了:100000
票卖完了:剩余:0卖出了:100000

(4).semaphore信号量

  • 首先要注意,信号量和信号是完全两码事。信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源。通常,程序对共享资源的访问的代码只是很短的一段,但就是这一段代码引发了进程之间的竞态条件。这段代码称为关键代码段,或者临界区。对进程同步,也就是确保任一时刻只有一个进程能进入关键代码段。
  • 信号量是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程A运行时,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这时信号量值为0。系统中规定当信号量值为0是,必须等待,直到信号量值不为零才能继续操作。 这时如果进程B想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就达到了进程A的排他访问。 当进程A运行结束后,释放资源,进行V操作。资源数重新加1,这时信号量的值变为1. 这时进程B发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.此时进程B占用资源,系统禁止其他进程访问资源。 这就是信号量来控制互斥的原理
  • 方法介绍
//创建一个信号量并制定其信号值
dispatch_semaphore_create(long value);
/*
1.如果信号值大于0 则向下执行任务
2.如果信号值等于0 则在timeout以前 当前进程一直处于阻塞状态 线程不可以访问
*/
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//信号量加1 唤醒处于wait状态的进程 线程可以访问
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
  • semaphore使用示例
- (IBAction)semaphoreMethod:(id)sender {
    self.soldedTicketsCount = 0;
    self.totalTicketsCount = 10000;
    //创建一个信号量 信号值设置为1
    self.semaphore = dispatch_semaphore_create(1);
    NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    firstThread.name = @"窗口一";
    NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    secondThread.name = @"窗口二";
    NSThread *thirdThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    thirdThread.name = @"窗口三";
    NSThread *fourthThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
    fourthThread.name = @"窗口四";
    [firstThread start];
    [secondThread start];
    [thirdThread start];
    [fourthThread start];
}
- (void)sellTickets_semaphore {
    while (1) {
        //当前信号值为1 向下执行任务并且信号值-1
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        if (self.totalTicketsCount > 0) {
            self.totalTicketsCount --;
            self.soldedTicketsCount ++;
            NSLog(@"%@:剩余:%ld卖出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
            //任务执行完毕 信号值为0需要加1 解除当前进程的阻塞状态 以便其他线程的访问
            dispatch_semaphore_signal(self.semaphore);
        } else {
            NSLog(@"票卖完了:剩余:%ld卖出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
            //任务执行完毕 信号值为0需要加1 解除当前进程的阻塞状态 以便其他线程的访问
            dispatch_semaphore_signal(self.semaphore);
            return;
        }
    }
}
控制台输出:
票卖完了:剩余:0卖出了:10000
票卖完了:剩余:0卖出了:10000
票卖完了:剩余:0卖出了:10000
票卖完了:剩余:0卖出了:10000
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容