iOS ------ 多线程 GCD

news2024/12/23 3:23:15

一,GCD简介

GCD是Apple开发的一个多线程的较新的解决方案。它主要用于优化应用程序以支持多核处理器以及其他对称处理系统。它是一个在线程池模式的基础上执行的并发任务。

为什么要使用GCD?

  • GCD!可用于多核的并行运算
  • GCD会自动利用更多的CPU内核(比如双核,四核)
  • GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

二,GCD任务和队列

  1. 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
  2. 队列:这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的 线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
    在这里插入图片描述
  3. 同步执行(sync):

同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1 -- %@", [NSThread currentThread]);
    // 串行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    NSLog(@"4 -- %@", [NSThread currentThread]);
    // Do any additional setup after loading the view.
}
输出
2024-05-29 20:53:55.168850+0800 GCD详解[69722:2675459] 1 -- <_NSMainThread: 0x600000b70100>{number = 1, name = main}
2024-05-29 20:53:55.168911+0800 GCD详解[69722:2675459] 2 -- <_NSMainThread: 0x600000b70100>{number = 1, name = main}
2024-05-29 20:53:55.168958+0800 GCD详解[69722:2675459] 3 -- <_NSMainThread: 0x600000b70100>{number = 1, name = main}
2024-05-29 20:53:55.169010+0800 GCD详解[69722:2675459] 4 -- <_NSMainThread: 0x600000b70100>{number = 1, name = m
  1. 异步执行(async):

异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1 -- %@", [NSThread currentThread]);
    // 串行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    NSLog(@"4 -- %@", [NSThread currentThread]);
    // Do any additional setup after loading the view.
}
输出
2024-05-29 21:02:04.468604+0800 GCD详解[69868:2682284] 1 -- <_NSMainThread: 0x60000376c540>{number = 1, name = main}
2024-05-29 21:02:04.468673+0800 GCD详解[69868:2682284] 4 -- <_NSMainThread: 0x60000376c540>{number = 1, name = main}
2024-05-29 21:02:04.468678+0800 GCD详解[69868:2682427] 2 -- <NSThread: 0x600003765580>{number = 6, name = (null)}
2024-05-29 21:02:04.468735+0800 GCD详解[69868:2682427] 3 -- <NSThread: 0x600003765580>{number = 6, name = (null)}
  1. 串行队列(Serial Dispatch Queue):

每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

  1. 并发队列(Concurrent Dispatch Queue):

可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

⚠️注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1 -- %@", [NSThread currentThread]);
    // 并行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    NSLog(@"4 -- %@", [NSThread currentThread]);
    // Do any additional setup after loading the view.
}
输出
024-05-29 21:03:45.087904+0800 GCD详解[69913:2684176] 1 -- <_NSMainThread: 0x600001d0c400>{number = 1, name = main}
2024-05-29 21:03:45.087962+0800 GCD详解[69913:2684176] 2 -- <_NSMainThread: 0x600001d0c400>{number = 1, name = main}
2024-05-29 21:03:45.087992+0800 GCD详解[69913:2684176] 3 -- <_NSMainThread: 0x600001d0c400>{number = 1, name = main}
2024-05-29 21:03:45.088034+0800 GCD详解[69913:2684176] 4 -- <_NSMainThread: 0x600001d0c400>{number = 1, name = main}

三, GCD 的使用步骤

GCD 的使用步骤只有两步:

1,创建一个队列(串行队列或并发队列);
2,将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。

3.1 队列的创建方法 / 获取方法

  • 可以使用 dispatch_queue_create 方法来创建队列。该方法需要传入两个参数:

第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
  • 对于串行队列,GCD 默认提供了:主队列(Main Dispatch Queue)。

1.所有放在主队列中的任务,都会放到主线程中执行。
2.可使用 dispatch_get_main_queue() 方法获得主队列。

⚠️注意:主队列其实并不特殊。 主队列的实质上就是一个普通的串行队列,只是因为默认情况下,平常写的代码是放在主队列中的,然后主队列中的代码,又都会放到主线程中去执行,所以才造成了主队列特殊的现象。

dispatch_queue_t queue = dispatch_get_main_queue();
  • 对于并发队列,GCD 默认提供了 全局并发队列(Global Dispatch Queue)。

可以使用 dispatch_get_global_queue 方法来获取全局并发队列。需要传入两个参数。第一个参数表示队列优先级,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可。

// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3.2 任务的创建方法

GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async。

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列 / 并发队列),两种任务执行方式(同步执行 / 异步执行),那么我们就有了四种不同的组合方式。这四种不同的组合方式是:

  1. 同步执行 + 并发队列
  2. 异步执行 + 并发队列
  3. 同步执行 + 串行队列
  4. 异步执行 + 串行队列

实际上,刚才还说了两种默认队列:全局并发队列主队列。全局并发队列可以作为普通并发队列来使用。但是当前代码默认放在主队列中,所以主队列很有必要专门来研究一下,这样就有六种不同的组合方式了。

  1. 同步执行 + 主队列
  2. 异步执行 + 主队列

3.3任务和队列不同组合方式的区别

3.3.1 同步执行 + 并发队列

在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。

NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
         [NSThread sleepForTimeInterval:2];  
         NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
         [NSThread sleepForTimeInterval:2];  
         NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
         [NSThread sleepForTimeInterval:2];  
         NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);

输出结果

2024-05-29 21:30:27.212376+0800 GCD详解[70435:2704510] 1 -- <_NSMainThread: 0x6000022584c0>{number = 1, name = main}
2024-05-29 21:30:27.212433+0800 GCD详解[70435:2704510] 2 -- <_NSMainThread: 0x6000022584c0>{number = 1, name = main}
2024-05-29 21:30:27.212479+0800 GCD详解[70435:2704510] 3 -- <_NSMainThread: 0x6000022584c0>{number = 1, name = main}
2024-05-29 21:30:27.212530+0800 GCD详解[70435:2704510] 4 -- <_NSMainThread: 0x6000022584c0>{number = 1, name = main}
2024-05-29 21:30:27.212569+0800 GCD详解[70435:2704510] 5 -- <_NSMainThread: 0x6000022584c0>{number = 1, name = main}

从 同步执行 + 并发队列 中可看到:

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
  • 所有任务都在打印的任务1和任务5之间执行的(同步任务 需要等待队列的任务执行结束)。
  • 任务按顺序执行的。按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务 不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
3.3.2 异步执行 + 并发队列
  • 可以开启多个线程,任务交替(同时)执行。
NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);

输出结果

024-05-29 21:36:49.626519+0800 GCD详解[70567:2709956] 1 -- <_NSMainThread: 0x600003d9c040>{number = 1, name = main}
2024-05-29 21:36:49.626591+0800 GCD详解[70567:2709956] 5 -- <_NSMainThread: 0x600003d9c040>{number = 1, name = main}
2024-05-29 21:36:51.631683+0800 GCD详解[70567:2710054] 2 -- <NSThread: 0x600003dd4c00>{number = 4, name = (null)}
2024-05-29 21:36:51.631683+0800 GCD详解[70567:2710050] 4 -- <NSThread: 0x600003dd4e00>{number = 5, name = (null)}
2024-05-29 21:36:51.631683+0800 GCD详解[70567:2710053] 3 -- <NSThread: 0x600003d9af40>{number = 3, name = (null)}

在 异步执行 + 并发队列 中可以看出:

  • 除了当前线程(主线程),系统又开启了 3 个线程,并且任务是交替/同时执行的。(异步执行 具备开启新线程的能力。且 并发队列 可开启多个线程,同时执行多个任务)。
  • 所有任务是在打印的任务1 和任务5之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行 不做等待,可以继续执行任务)。
3.3.3 同步执行 + 串行队列
  • 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);

输出结果

2024-05-29 21:40:14.290797+0800 GCD详解[70741:2714482] 1 -- <_NSMainThread: 0x60000362c400>{number = 1, name = main}
2024-05-29 21:40:16.291817+0800 GCD详解[70741:2714482] 2 -- <_NSMainThread: 0x60000362c400>{number = 1, name = main}
2024-05-29 21:40:18.292859+0800 GCD详解[70741:2714482] 3 -- <_NSMainThread: 0x60000362c400>{number = 1, name = main}
2024-05-29 21:40:20.293898+0800 GCD详解[70741:2714482] 4 -- <_NSMainThread: 0x60000362c400>{number = 1, name = main}
2024-05-29 21:40:20.293968+0800 GCD详解[70741:2714482] 5 -- <_NSMainThread: 0x60000362c400>{number = 1, name = main}

在 同步执行 + 串行队列 可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行 不具备开启新线程的能力)。
  • 所有任务都在打印的任务1和任务5之间执行(同步任务 需要等待队列的任务执行结束)。
  • 任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。
3.3.4异步执行 + 串行队列
  • 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);

输出结果:

2024-05-29 21:43:18.017856+0800 GCD详解[70835:2717791] 1 -- <_NSMainThread: 0x6000037b8000>{number = 1, name = main}
2024-05-29 21:43:18.017922+0800 GCD详解[70835:2717791] 5 -- <_NSMainThread: 0x6000037b8000>{number = 1, name = main}
2024-05-29 21:43:20.023459+0800 GCD详解[70835:2717899] 2 -- <NSThread: 0x6000037f0540>{number = 5, name = (null)}
2024-05-29 21:43:22.028921+0800 GCD详解[70835:2717899] 3 -- <NSThread: 0x6000037f0540>{number = 5, name = (null)}
2024-05-29 21:43:24.033872+0800 GCD详解[70835:2717899] 4 -- <NSThread: 0x6000037f0540>{number = 5, name = (null)}

在 异步执行 + 串行队列 可以看到:

  • 开启了一条新线程(异步执行 具备开启新线程的能力,串行队列 只开启一个线程)。
  • 所有任务是在打印的任务1和任务5之后才开始执行的(异步执行 不会做任何等待,可以继续执行任务)。
  • 任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。
3.3.5同步执行 + 主队列
  • 同步执行 + 主队列 在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会。
  • 主队列:
    默认情况,平常所写的代码是直接放在主队列中的
    所有放在主队列中的任务,都会在主线程中执行
    可使用dispatch_get_main_queue() 获得主队列
3.3.5.1 在主线程中调用 同步执行 + 主队列
  • 互相等待卡住不可行
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);
}

这样会直接崩溃

这是因为我们在主线程中执行任务5方法,相当于把任务5放到了主线程的队列中。而 同步执行 会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把 任务2追加到主队列中,任务2就在等待主线程处理完5任务。而任务5需要等待任务1 所在队列的所有任务执行完毕,才能接着执行。这样相互等待,就造成了死锁

3.3.5.2在其他线程中调用『同步执行 + 主队列』
  • 不会开启新线程,执行完一个任务,再执行下一个任务
- (void)viewDidLoad {
    [super viewDidLoad];
    [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
    
}
- (void)syncMain {
    NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);
 }

输出结果:

2024-05-29 21:59:50.168185+0800 GCD详解[71147:2730839] 1 -- <NSThread: 0x6000027bc700>{number = 7, name = (null)}
2024-05-29 21:59:52.197328+0800 GCD详解[71147:2730626] 2 -- <_NSMainThread: 0x6000027d0000>{number = 1, name = main}
2024-05-29 21:59:54.203257+0800 GCD详解[71147:2730626] 3 -- <_NSMainThread: 0x6000027d0000>{number = 1, name = main}
2024-05-29 21:59:56.210364+0800 GCD详解[71147:2730626] 4 -- <_NSMainThread: 0x6000027d0000>{number = 1, name = main}
2024-05-29 21:59:56.210959+0800 GCD详解[71147:2730839] 5 -- <NSThread: 0x6000027bc700>{number = 7, name = (null)}

在其他线程中使用 同步执行 + 主队列 可看到:

  • 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
  • 所有任务都在打印的任务1和任务5之间执行(同步任务 需要等待队列的任务执行结束)。
  • 任务是按顺序执行的(主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

这里任务2不用等待其他线程的任务5执行完,不会造成死锁。

3.3.6 异步执行 + 主队列
  • 只在主线程执行任务,执行完一个任务,在执行下一个任务
NSLog(@"1 -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3 -- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4 -- %@",[NSThread currentThread]);
    });
    NSLog(@"5 -- %@", [NSThread currentThread]);

输出结果:

2024-05-30 19:47:21.114444+0800 GCD详解[96320:3468801] 1 -- <_NSMainThread: 0x6000011780c0>{number = 1, name = main}
2024-05-30 19:47:21.114507+0800 GCD详解[96320:3468801] 5 -- <_NSMainThread: 0x6000011780c0>{number = 1, name = main}
2024-05-30 19:47:23.140562+0800 GCD详解[96320:3468801] 2 -- <_NSMainThread: 0x6000011780c0>{number = 1, name = main}
2024-05-30 19:47:25.142281+0800 GCD详解[96320:3468801] 3 -- <_NSMainThread: 0x6000011780c0>{number = 1, name = main}
2024-05-30 19:47:27.143875+0800 GCD详解[96320:3468801] 4 -- <_NSMainThread: 0x6000011780c0>{number = 1, name = main}

在 异步执行 + 主队列 可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然 异步执行 具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。
  • 所有任务是在打印的任务1和任务5之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
  • 任务是按顺序执行的(因为主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

四,GCD线程间的通信

在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

//获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //获取主队列
    dispatch_queue_t mainqueue = dispatch_get_main_queue();
    NSLog(@"begin---%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        //异步追加任务1
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
        
        //回到主线程
        dispatch_async(mainqueue, ^{
            //追加到主线程中2执行任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@", [NSThread currentThread]);
        });
        NSLog(@"3---%@", [NSThread currentThread]);
    });
    NSLog(@"end---%@", [NSThread currentThread]);

输出结果

2024-05-30 20:15:39.553857+0800 GCD详解[96959:3489760] begin---<_NSMainThread: 0x600000a0c100>{number = 1, name = main}
2024-05-30 20:15:39.553924+0800 GCD详解[96959:3489760] end---<_NSMainThread: 0x600000a0c100>{number = 1, name = main}
2024-05-30 20:15:41.559224+0800 GCD详解[96959:3490016] 1---<NSThread: 0x600000a4dd40>{number = 8, name = (null)}
2024-05-30 20:15:41.559701+0800 GCD详解[96959:3490016] 3---<NSThread: 0x600000a4dd40>{number = 8, name = (null)}
2024-05-30 20:15:43.560499+0800 GCD详解[96959:3489760] 2---<_NSMainThread: 0x600000a0c100>{number = 1, name = main}
  • 可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。

六,GCD信号量: dispatch_semaphore

Dispatch Semaphore(调度信号量)是 GCD(Grand Central Dispatch)提供的一种同步机制,用于控制并发访问资源或者在不同线程之间进行同步。信号量可以用来限制并发执行的任务数量或者让一个线程等待某个事件的发生

使用 Dispatch Semaphore

  • 创建信号量

dispatch_semaphore_create 用于创建一个信号量,初始计数可以是任意非负整数。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // 创建初始值为1的信号量
  • 等待信号量

dispatch_semaphore_wait 用于等待信号量,如果信号量计数大于0,则减1并立即返回;否则阻塞当前线程,直到信号量计数大于0或者超时。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量,永远阻塞直到信号量计数大于0
  • 发送信号

dispatch_semaphore_signal 用于发送信号,即增加信号量的计数。如果有等待的线程,则唤醒一个等待的线程。

dispatch_semaphore_signal(semaphore); // 发送信号,增加信号量计数
控制并发任务数量

假设你想限制最多只有2个任务同时执行,可以使用信号量来实现:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(2); // 最多允许2个任务同时执行

for (int i = 0; i < 5; i++) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量
        NSLog(@"Task %d started", i);
        sleep(2); // 模拟任务耗时2秒
        NSLog(@"Task %d completed", i);
        dispatch_semaphore_signal(semaphore); // 发送信号
    });
}
实现线程同步,将异步执行任务转换为同步执行任务
NSLog(@"currentThread -- %@", [NSThread currentThread]);
    NSLog(@"semaphere --- begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphere = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1 --- %@", [NSThread currentThread]);
        
        number = 100;
        
        dispatch_semaphore_signal(semaphere);
    });
    dispatch_semaphore_wait(semaphere, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore --- end  number = %d", number);

输出结果:

2024-05-30 21:27:19.082058+0800 GCD详解[98426:3541645] currentThread -- <_NSMainThread: 0x6000030f4000>{number = 1, name = main}
2024-05-30 21:27:19.082108+0800 GCD详解[98426:3541645] semaphere --- begin
2024-05-30 21:27:21.087427+0800 GCD详解[98426:3541817] 1 --- <NSThread: 0x6000030b2380>{number = 4, name = (null)}
2024-05-30 21:27:21.087923+0800 GCD详解[98426:3541645] semaphore --- end  number = 100

semaphore—end 是在执行完 number = 100; 之后才打印的。而且输出结果 number 为 100。
执行顺如下:

  1. semaphore 初始创建时计数为 0。
  2. 异步执行 将 任务 1 追加到队列之后,不做等待,接着执行 dispatch_semaphore_wait 方法,semaphore 减 1,此时 semaphore == -1,当前线程进入等待状态。
  3. 然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal 之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程(主线程)恢复继续执行,最后打印 semaphore—end,number = 100。

异步执行任务转换为同步执行任务,可以对异步的执行结果进行进一步操作

Dispatch Semaphore 在实际开发中主要用于:

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

七,Dispatch Semaphore 线程安全和线程同步(为线程加锁)

非线程安全(不使用 semaphore)

先来看看不考虑线程安全的代码:

/**
 * 非线程安全:不使用 semaphore
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });

/**
 * 售卖火车票(非线程安全)
 */
- (void)saleTicketNotSafe {
    while (1) {
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            break;
        }
        
    }
}

线程安全(使用 semaphore 加锁)

/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

运行结果

2024-05-30 21:54:54.019858+0800 GCD详解[99036:3563863] 剩余票数:10 窗口:<NSThread: 0x6000036c0dc0>{number = 5, name = (null)}
2024-05-30 21:54:54.222849+0800 GCD详解[99036:3563864] 剩余票数:9 窗口:<NSThread: 0x600003681bc0>{number = 3, name = (null)}
2024-05-30 21:54:54.428311+0800 GCD详解[99036:3563863] 剩余票数:8 窗口:<NSThread: 0x6000036c0dc0>{number = 5, name = (null)}
2024-05-30 21:54:54.632216+0800 GCD详解[99036:3563864] 剩余票数:7 窗口:<NSThread: 0x600003681bc0>{number = 3, name = (null)}
2024-05-30 21:54:54.833208+0800 GCD详解[99036:3563863] 剩余票数:6 窗口:<NSThread: 0x6000036c0dc0>{number = 5, name = (null)}
2024-05-30 21:54:55.037057+0800 GCD详解[99036:3563864] 剩余票数:5 窗口:<NSThread: 0x600003681bc0>{number = 3, name = (null)}
2024-05-30 21:54:55.237681+0800 GCD详解[99036:3563863] 剩余票数:4 窗口:<NSThread: 0x6000036c0dc0>{number = 5, name = (null)}
2024-05-30 21:54:55.439402+0800 GCD详解[99036:3563864] 剩余票数:3 窗口:<NSThread: 0x600003681bc0>{number = 3, name = (null)}
2024-05-30 21:54:55.643820+0800 GCD详解[99036:3563863] 剩余票数:2 窗口:<NSThread: 0x6000036c0dc0>{number = 5, name = (null)}
2024-05-30 21:54:55.849417+0800 GCD详解[99036:3563864] 剩余票数:1 窗口:<NSThread: 0x600003681bc0>{number = 3, name = (null)}
2024-05-30 21:54:56.055054+0800 GCD详解[99036:3563863] 剩余票数:0 窗口:<NSThread: 0x6000036c0dc0>{number = 5, name = (null)}
2024-05-30 21:54:56.260555+0800 GCD详解[99036:3563864] 所有火车票均已售完
2024-05-30 21:54:56.261010+0800 GCD详解[99036:3563863] 所有火车票均已售完

semaphoreLock = dispatch_semaphore_create(1);这里的信号量设置为1,就是为了保证只有一个线程同时运行售卖火车票的这段代码。保证了线程安全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1717656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

dockers安装mysql

1.dockerhub上搜索自己需要安装得镜像版本 dockerhub网址&#xff1a;https://hub-stage.docker.com docker pull mysql:5.7 #下载自己需要得版本2.启动容器实例&#xff0c;并且挂载容器数据卷 docker run -d -p 3306:3306 --privilegedtrue \ -v /home/mysql/log:/var/log/…

模拟 CMOS 逆变器的开关功耗

我们不会进一步讨论静态功耗。相反&#xff0c;本文和下一篇文章将介绍 SPICE 仿真&#xff0c;以帮助您更全面地了解逆变器的不同类型的动态功耗。本文重点讨论开关功率——输出电压变化时电容充电和放电所消耗的功率。 LTspice 逆变器实施 图 1 显示了我们将使用的基本 LTsp…

小白跟做江科大32单片机之OLED驱动

原理部分 代码测试 1.江科大老师提供的以下代码文件放入工程中&#xff0c;进行测试 2.正常显示即可

一点连接千家银行,YonSuite让“企业资金”实时在线

用友YonSuite作为全场景SaaS应用服务&#xff0c;是成长型企业实现数智转型的不二选择。多年来&#xff0c;凭借不断的技术革新&#xff0c;通过与千家银行的一站式连接&#xff0c;实现了企业资金的实时在线管理&#xff0c;为成长型企业带来了极大的便利和效益。这一举措不仅…

618适合入手哪些数码好物?实用数码好物清单分享,错过拍烂大腿!

在一年一度的618购物狂欢节里&#xff0c;许多数码爱好者们都在这次盛大的购物盛宴中觅得心仪的数码好物&#xff0c;数码产品不仅改变了我们的生活方式&#xff0c;更让我们享受到了前所未有的便捷和乐趣&#xff0c;那么在这个618&#xff0c;哪些数码好物值得我们入手呢&…

在线IP检测如何做?代理IP需要检查什么?

当我们的数字足迹无处不在&#xff0c;隐私保护显得愈发重要。而代理IP就像是我们的隐身斗篷&#xff0c;让我们在各项网络业务中更加顺畅。 我们常常看到别人购买了代理IP服务后&#xff0c;通在线检测网站检查IP&#xff0c;相当于一个”售前检验““售后质检”的作用。但是…

Golang:使用Base64Captcha生成数字字母验证码实现安全校验

Base64Captcha可以在服务端生成验证码&#xff0c;以base64的格式返回 为了能看到生成的base64验证码图片&#xff0c;我们借助gin go get -u github.com/mojocn/base64Captcha go get -u github.com/gin-gonic/gin文档的示例看起来很复杂&#xff0c;下面&#xff0c;通过简…

豆包浏览器插件会造成code标签内容无法正常显示

启用状态&#xff1a;页面的代码会显示不正常 禁用后&#xff0c;正常显示 害得我重置浏览器设置&#xff0c;一个个测试

leetcode刷题记录28-427. 建立四叉树

问题描述 给你一个 n * n 矩阵 grid &#xff0c;矩阵由若干 0 和 1 组成。请你用四叉树表示该矩阵 grid 。 你需要返回能表示矩阵 grid 的 四叉树 的根结点。 四叉树数据结构中&#xff0c;每个内部节点只有四个子节点。此外&#xff0c;每个节点都有两个属性&#xff1a; val…

JVM学习-字节码指令集(三)

代码下载 操作数栈管理指令 如同操作一个普通数据结构中的堆栈那样&#xff0c;JVM提供的操作数栈管理指令&#xff0c;可以用于直接操作数栈的指令 将一个或两个元素从栈顶弹出&#xff0c;并且直接废弃&#xff1a;pop,pop2复制栈顶一个或两个数值并将复制值成双份的复制值…

【全开源】餐饮点餐系统源码(ThinkPHP+FastAdmin+UniApp)

开启智能餐饮新时代的钥匙 基于ThinkPHPFastAdminUniApp开发的餐饮点餐系统&#xff0c;主要应用于餐饮&#xff0c;例如早餐、面馆、快餐、零食小吃等快捷扫码点餐需求&#xff0c;标准版本仅支持先付款后就餐模式&#xff0c;高级版本支持先付后就餐和先就餐后付费两种模式。…

昆虫记思维导图,超详细解读

《昆虫记》是法国杰出昆虫学家、文学家法布尔的传世佳作&#xff0c;它不仅是一部研究昆虫的科学巨著&#xff0c;同时也是一部脍炙人口的文学经典。在这部作品中&#xff0c;法布尔以其独特的视角和细腻的笔触&#xff0c;为我们揭示了一个神秘而精彩的昆虫世界。那么&#xf…

重生之 SpringBoot3 入门保姆级学习(11、日志的进阶使用)

重生之 SpringBoot3 入门保姆级学习&#xff08;11、日志的进阶使用&#xff09; 3.2.4 文件输出3.2.5 日志文档的归档与切割 3.2.4 文件输出 配置 application.properties # 日志文件名 如果不写路径默认就是在项目根路径建立 demo.log 文件 推荐写法 D:\\demo.log 路径 文…

为什么要使用动态代理IP?

一、什么是动态代理IP&#xff1f; 动态代理IP是指利用代理服务器来转发网络请求&#xff0c;并通过不断更新IP地址来保护访问者的原始IP&#xff0c;从而达到匿名访问、保护隐私和提高访问安全性的目的。动态代理IP在多个领域中都有广泛的应用&#xff0c;能够帮助用户…

面试题:计算机网络中的七四五是什么?

面试题&#xff1a;计算机网络中的七四五是什么&#xff1f; 计算机网络中说的七四五是指&#xff1a;OSI 七层模型、TCP/IP 四层模型、OSI 与 TCP/IP 的综合五层模型 OSI 七层模型 OSI 将计算机网络分为了七层&#xff0c;每一层抽象底层的内容&#xff0c;并遵守一定的规则…

【错题集-编程题】过桥(BFS)

牛客对应题目链接&#xff1a;过桥 (nowcoder.com) 一、分析题目 类似层序遍历的思想。 二、代码 //值得学习的代码 #include <iostream>using namespace std;const int N 2010;int n; int arr[N];int bfs() {int left 1, right 1;int ret 0;while(left < right)…

PMP认证与NPDP认证哪个含金量高?

PMP和NPDP&#xff0c;哪个含金量更高呢&#xff1f; PMP可以全面提升你的职业发展&#xff0c;无论你是技术人员还是项目管理人员&#xff0c;都能帮助你打破思维定式&#xff0c;拓宽视野&#xff0c;并提升管理水平和领导能力。 NPDP不仅帮助个人了解新产品开发流程和原理…

分布式锁的原理和实现(Go)

文章目录 思维导图为什么需要分布式锁&#xff1f;go语言分布式锁的实现Redis自己的实现单元测试 红锁是什么别人的带红锁的实现 etcdzk的实现 面试问题什么是分布式锁&#xff1f;你用过分布式锁吗&#xff1f;你使用的分布式锁性能如何&#xff0c;可以优化吗&#xff1f;怎么…

为什么说OV SSL比DV SSL好

OV SSL证书和DV SSL证书是两种常见的SSL证书类型&#xff0c;它们在验证深度、安全性和可见性等方面存在差异。下面是具体分析&#xff1a; 验证深度 DV SSL&#xff1a;只进行域名所有权的验证。 OV SSL&#xff1a;除了验证域名所有权&#xff0c;还需要验证企业信息。 安…

2024年人文发展与社会科学国际会议(ICHDSS 2024)

2024年人文发展与社会科学国际会议 2024 International Conference on Humanities Development and Social Sciences 【1】会议简介 2024年人文发展与社会科学国际会议是一个汇集全球人文科学和社会科学领域专家学者的盛会。本次会议旨在深入探讨人文发展的多元性、复杂性以及社…