iOS ------ 多线程 GCD

一,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.mzph.cn/diannao/19672.shtml

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

相关文章

Linux配置java,maven,marshalsec环境

文章目录 一. Linux配置java环境1.下载jdk文件2.解压tar.gz文件3.设置java环境变量4.验证是否成功 二. Linux配置maven环境1.下载压缩包2.解压tar.gz3. 配置环境变量 三. Linux配置marshalsec环境 一. Linux配置java环境 1.下载jdk文件 mkdir /opt/javawget https://repo.hua…

STM32使用ST-LINK下载程序中需要注意的几点

使用keil5的ST-link下载界面 前提是ST-LINK已经连接好&#xff0c;&#xff08;下图中是没有连接ST-link设备&#xff09;&#xff0c;只是为了展示如何查看STlink设备是否连接的方式 下载前一定设置下载完成后自启动 这个虽然不是必须&#xff0c;但对立即看到新程序的现象…

CVE-2020-0688 远程代码执行漏洞

CVE-2020-0688 远程代码执行漏洞 漏洞产生的主要原因就是在Exchange ECP组件中发现&#xff0c;邮件服务在安装的过程中不会随机生成秘钥&#xff0c;也就是说所有默认安装的Exchange服务器中的validationKey和decryptionKey的值都是相同的&#xff0c;攻击者可以利用静态秘钥…

【深度学习基础】使用Pytorch搭建DNN深度神经网络与手写数字识别

目录 写在开头 一、DNN的搭建 问题描述与数据集 神经网络搭建 模型训练 模型评估 模型复用 二、手写数字识别 任务描述 数据集 神经网络搭建 模型训练 模型评估 写在最后 写在开头 本文将介绍如何使用PyTorch框架搭建深度神经网络模型。实现模型的搭建、模…

USART串口外设

USART介绍 USART&#xff1a;另外我们经常还会遇到串口&#xff0c;叫UART&#xff0c;少了个S&#xff0c;就是通用异步收发器&#xff0c;一般我们串口很少使用这个同步功能&#xff0c;所以USART和UART使用起来&#xff0c;也没有什么区别。 其实这个STM32的USART同步模式&a…

创新产品认定进行第三方软件鉴定测试的原因

鉴定测试报告 随着科技的飞速发展&#xff0c;软件产品在各个领域的应用越来越广泛&#xff0c;对于软件产品的质量和安全性要求也越来越高。为了确保软件产品的质量和安全性&#xff0c;创新产品认定进行第三方软件鉴定测试成为了一种必要手段。 一、保障产品质量 第三方软…

从了解到掌握 Spark 计算框架(二)RDD

文章目录 RDD 概述RDD 组成RDD 的作用RDD 算子分类RDD 的创建1.从外部数据源读取2.从已有的集合或数组创建3.从已有的 RDD 进行转换 RDD 常用算子大全转换算子行动算子 RDD 算子综合练习RDD 依赖关系窄依赖宽依赖宽窄依赖算子区分 RDD 血统信息血统信息的作用血统信息的组成代码…

LM2733升压芯片

具有 40V 内部 FET 开关且采用 SOT-23 封装的 LM2733 0.6MHz 和 1.6MHz 升压转换器 外观 参考价格 1 特性 电路原理图 基于LM2733升压电路设计-CSDN博客https://blog.csdn.net/qq_31251431/article/details/107479885 特此记录 anlog 2024年5月31日 高压方案 此方案经过更多…

MySQL -- SQL笔试题相关

1.银行代缴花费bank_bill 字段名描述serno流水号date交易日期accno账号name姓名amount金额brno缴费网点 serno: 一个 BIGINT UNSIGNED 类型的列&#xff0c;作为主键&#xff0c;且不为空。该列是自动增量的&#xff0c;每次插入新行时&#xff0c;都会自动递增生成一个唯一的…

游戏安全 | 一款「安全」的SLG游戏应该是什么样的?

谈到SLG游戏&#xff0c;也许会想到《万国觉醒》&#xff0c;海外上线5个月后&#xff0c;以5400万美元的月流水创造了新的SLG手游海外收入纪录。 谈到SLG游戏&#xff0c;也许会想到《王国纪元》&#xff0c;通过两军对战的方式&#xff0c;以大面积消灭敌人的攻势&#xff0c…

数据库开发-MySQL01

目录 前言 1. MySQL概述 1.1 安装 1.1.1 版本 1.1.2 安装 1.1.3 连接 1.1.4 企业使用方式(了解) 1.2 数据模型 1.3 SQL简介 1.3.1 SQL通用语法 1.3.2 分类 2. 数据库设计-DDL 2.1 项目开发流程 2.2 数据库操作 2.2.1 查询数据库 2.2.2 创建数据库 2.2.3 使用数…

echarts学习:将echats实例代理为响应式对象可能带来的风险

1.起源 最近我在学习如何封装echarts组件&#xff0c;我所参考的其中一篇博客中提到了一个“图表无法显示的问题”。 根据其中的介绍&#xff0c;造成此种问题的原因是因为&#xff0c;使用ref接受了echarts实例&#xff0c;使得echarts实例被代理为了响应式对象&#xff0c;进…

ChatGPT-4o在临床医学日常工作、论文高效撰写与项目申报、数据分析与可视化、机器学习建模中的应用

ChatGPT-4o在临床医学日常工作、论文高效撰写与项目申报、数据分析与可视化、机器学习建模中的应用 2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT-3.5&#xff0c;将人工智能的发展推向了一个新的高度。…

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试5月29日预测第5弹

今天继续基于8883的大底&#xff0c;使用尽可能少的条件进行缩号&#xff0c;同时&#xff0c;同样准备两套方案&#xff0c;一套是我自己的条件进行缩号&#xff0c;另外一套是8883的大底结合2码不定位奖号预测二次缩水来杀号。好了&#xff0c;直接上结果吧~ 首先&…

BUUCTF Crypto RSA详解《1~32》刷题记录

文章目录 一、Crypto1、 一眼就解密2、MD53、Url编码4、看我回旋踢5、摩丝6、password7、变异凯撒8、Quoted-printable9、篱笆墙的影子10、Rabbit11、RSA12、丢失的MD513、Alice与Bob14、大帝的密码武器15、rsarsa16、Windows系统密码17、信息化时代的步伐18、凯撒&#xff1f;…

如何使用视频号下载助手机器人,下载视频号视频

目录 微信视频号版权问题 视频号下载助手机器人如何获取 手机市场基本一年每个品牌商发布的手机就高达10多种&#xff0c;而这些设备中并不支持手机缓存操作&#xff0c;却把市场搞的越来越浑&#xff0c;还不断宣传手机缓存可保存视频&#xff0c;今天教教大家如何使用视频号…

私域加持业务 快消门店运营新玩法

两个月前&#xff0c;某快消品企业的李总急切地联系了纷享销客&#xff0c;希望能找到解决终端门店运营难题的有效方法。 Step1、连接终端门店&#xff0c;导入私域进行深度维系与运营 一、与终端门店建立联系 为了与众多门店老板建立紧密的联系&#xff0c;并将他们转化为企…

sqliteSQL基础

SQL基础 SQLite 数据库简介 SQLite 是一个开源的、 内嵌式的关系型数据库&#xff0c; 第一个版本诞生于 2000 年 5 月&#xff0c; 目前最高版本为 SQLite3。 下载地址&#xff1a; https://www.sqlite.org/download.html 菜鸟教程 : https://www.runoob.com/sqlite/sqlit…

Redis相关详解

什么是 Redis&#xff1f;它主要用来什么✁&#xff1f; Redis&#xff0c;英文全称是 Remote Dictionary Server&#xff08;远程字典服务&#xff09;&#xff0c;是一个开源✁使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化✁日志型、Key-Value 数据库&#xff…

Elasticsearch 认证模拟题 -2

一、题目 有一个索引 task3&#xff0c;其中有 fielda&#xff0c;fieldb&#xff0c;fieldc&#xff0c;fielde 现要求对 task3 重建索引&#xff0c;重建后的索引新增一个字段 fieldg 其值是fielda&#xff0c;fieldb&#xff0c;fieldc&#xff0c;fielde 的值拼接而成。 …