当前位置: 首页 > news >正文

并发编程 - GCD信号量

引言

在现代应用开发中,处理并发任务已经成了不可避免的挑战。在这种情况下,如何有效地管理多个线程对共享资源的访问,避免资源竞争和数据不一致的问题,成为了我们必须面对的难题。在NSOperation&NSOperationQueue中系统为我们提供了控制最大并发数,设置操作依赖,和线程等待等方法。GCD也同样提供了一系列强大的工具来帮助我们解决这些问题,在前面的博客中我们已经介绍了一个栅栏,本篇博客我们就来介绍另外一个关键的的工具——信号量(Semaphore)。

信号量的概念

信号量(Semaphore)是并发编程中的关键工具,用于控制多个线程对共享资源的访问。它通过维护一个计数来限制同时访问某个资源的线程数量,确保资源不会被过渡占用。信号量的工作方式有点类似于NSOperationQueue的最大并发数属性,但不同的是,信号量的技术增加和减少需要开发者手动管理。

在Objective-C中信号量是dispatch_semaphore_t类型,而在Swift中,信号量是DispatchSemaphore类的实例。初始化时,信号量包含一个计数值,这个值表示可以同时访问资源的线程数。开发者可以通过增加或减少技术来手动管理资源的占用和释放,确保线程按照预期的方式等待资源的可用性。

以Swift为例,信号量有两个核心的方法wait()和signal()。

  • wait()方法用于减少信号量的计数,通常用于控制对有限资源的访问。
  • signal()方法用于增加信号量的计数,通常在资源使用完毕后调用。

信号量的用法

信号量使用很简单,因为它只有三个方法:

  • 创建信号量:DispatchSemaphore(value: 1)
  • 减少信号量计数:wait()
  • 增加信号量计数:signal()

但是知道在什么场景来灵活使用它却并不容易,最简单的用法就是通过初始化一个计数值为1的信号量,将异步操作变为同步操作来保证线程安全。这种方式适用于需要确保只有一个线程能访问共享资源的场景,比如读写文件或更新数据库记录的操作。

代码如下:

    func semaphore() {let semaphore = DispatchSemaphore(value: 1)let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)concurrentQueue.async {semaphore.wait()print("Task 1")sleep(1)semaphore.signal()}concurrentQueue.async {semaphore.wait()print("Task 2")sleep(1)semaphore.signal()}}

上面的代码虽然是并发异步,但由于信号量的存在Task 2 仍然会在Task 1执行完1秒后才执行。

但是如果我们将初始化计数改为2,则Task 1和Task 2会同时执行。

实际应用场景

在应用的实际开发中有很多场景都需要使用到信号量来保证线程安全,提升程序性能,下面我们就来列举几个典型的场景。

多个窗口售票问题

我们首先来列举一个比较典型的多个窗口同时售票的场景。

假设有3个窗口同时出售同一场电影的门票,门票总数为100张,如果我们不使用信号量来保证线程安全,实现代码如下:

    /// 总票数private var ticketCount = 100//MARK: 开始售票func startSaleTicket() {print("开始售票 当前线程:\(Thread.current)")ticketCount = 100let queue1 = DispatchQueue(label: "com.example.queue1")let queue2 = DispatchQueue(label: "com.example.queue2")let queue3 = DispatchQueue(label: "com.example.queue3")queue1.async {self.saleTicket()}queue2.async {self.saleTicket()}queue3.async {self.saleTicket()}}//MARK: 模拟售票private func saleTicket() {while true {if ticketCount <= 0 {print("所有票已售完")break}ticketCount = ticketCount - 1print("剩余票数:\(ticketCount) 窗口:\(Thread.current)")}}

执行结果如下:

开始售票 当前线程:<_NSMainThread: 0x600000b60500>{number = 1, name = main}

剩余票数:98 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:97 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:96 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:94 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:93 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:92 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:91 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:90 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:89 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:88 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:87 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:86 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:85 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:84 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:93 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:99 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:81 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:82 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:83 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:78 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:79 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:80 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:76 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:77 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:74 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:72 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:73 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:71 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:69 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:70 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:75 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:67 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:66 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:68 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:65 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:64 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:63 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:62 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:61 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:59 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:57 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:58 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:56 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:55 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:60 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:53 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:52 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:54 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:50 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:51 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:49 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:48 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:47 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:45 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:44 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:43 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:42 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:41 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:46 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:40 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:38 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:39 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:37 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:36 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:34 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:32 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:35 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:33 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:31 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:30 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:28 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:29 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:27 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:26 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:25 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:24 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:23 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:22 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:20 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:21 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:18 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:17 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:16 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:14 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:19 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:13 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:15 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:11 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:10 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:12 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:9 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:8 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:7 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:6 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:5 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

剩余票数:4 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

剩余票数:3 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

剩余票数:2 窗口:<NSThread: 0x600000b2c100>{number = 10, name = (null)}

所有票已售完

剩余票数:0 窗口:<NSThread: 0x600000b3c6c0>{number = 8, name = (null)}

所有票已售完

剩余票数:1 窗口:<NSThread: 0x600000b39c00>{number = 9, name = (null)}

所有票已售完

仔细看结果就会发现在剩余84张票之后,又突然剩余93张了,93张之后又突然剩余99张了,这明显是不符合正常销售场景的,因为票只会越来越少才对。而在最后有的窗口已经表示没有剩余票数了,有的窗口却又卖出去一张,这显然是不符合正常售票流程的。

下面我们就用信号量加锁来改善这个问题,代码如下:

    /// 总票数private var ticketCount = 100/// 信号量private var semaphore = DispatchSemaphore(value: 1)//MARK: 开始售票func startSaleTicket() {print("开始售票 当前线程:\(Thread.current)")ticketCount = 100let queue1 = DispatchQueue(label: "com.example.queue1")let queue2 = DispatchQueue(label: "com.example.queue2")let queue3 = DispatchQueue(label: "com.example.queue3")queue1.async {self.saleTicket()}queue2.async {self.saleTicket()}queue3.async {self.saleTicket()}}//MARK: 模拟售票private func saleTicket() {while true {semaphore.wait()if ticketCount <= 0 {print("所有票已售完")semaphore.signal()break}ticketCount = ticketCount - 1print("剩余票数:\(ticketCount) 窗口:\(Thread.current)")semaphore.signal()}}

结果如下:

开始售票 当前线程:<_NSMainThread: 0x600000bf01c0>{number = 1, name = main}

剩余票数:99 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:98 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:97 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:96 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:95 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:94 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:93 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:92 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:91 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:90 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:89 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:88 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:87 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:86 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:85 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:84 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:83 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:82 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:81 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:80 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:79 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:78 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:77 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:76 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:75 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:74 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:73 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:72 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:71 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:70 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:69 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:68 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:67 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:66 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:65 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:64 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:63 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:62 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:61 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:60 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:59 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:58 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:57 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:56 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:55 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:54 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:53 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:52 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:51 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:50 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:49 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:48 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:47 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:46 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:45 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:44 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:43 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:42 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:41 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:40 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:39 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:38 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:37 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:36 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:35 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:34 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:33 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:32 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:31 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:30 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:29 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:28 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:27 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:26 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:25 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:24 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:23 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:22 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:21 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:20 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:19 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:18 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:17 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:16 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:15 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:14 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:13 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:12 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:11 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:10 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:9 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:8 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:7 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:6 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:5 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:4 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:3 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

剩余票数:2 窗口:<NSThread: 0x600000ba0080>{number = 9, name = (null)}

剩余票数:1 窗口:<NSThread: 0x600000ba6c00>{number = 10, name = (null)}

剩余票数:0 窗口:<NSThread: 0x600000ba6b40>{number = 8, name = (null)}

所有票已售完

所有票已售完

所有票已售完

这次我们发现,虽然三个线程同时售票,但票的数量没有任何错乱问题。

线程安全的队列

在项目的实际开发中,一个线程安全的队列使用频率还是非常高的,当有过多的消息或者是动画需要处理时,用于性能原因或者其他方面考虑,我们并不会一次性让所有内容都展示出来,而是通过让它们进入一个队列,再从队列内一个一个取出来处理或者是播放。但我们并不能保证每个消息或者是动画资源都是从同一个线程来的,因此我们需要一定的手段来保证队列的线程安全。

import Foundationclass SafeQueue<T> {private var queue: [T] = []private let semaphore = DispatchSemaphore(value: 1) // 信号量初始值为1,表示只有一个线程可以访问队列// 入队操作func enqueue(_ element: T) {semaphore.wait() // 请求访问权限queue.append(element)semaphore.signal() // 释放访问权限}// 出队操作func dequeue() -> T? {semaphore.wait() // 请求访问权限let element = queue.isEmpty ? nil : queue.removeFirst()semaphore.signal() // 释放访问权限return element}// 获取队列的大小func size() -> Int {semaphore.wait() // 请求访问权限let size = queue.countsemaphore.signal() // 释放访问权限return size}
}

我们使用DispatchSemaphore(value: 1)来创建一个初始计数为1的信号量,这意味着队列的访问是互斥的,任何时刻只有一个线程能够执行enqueue或者dequeue操作。

enqueue方法,在添加元素到队列之前,线程会调用semaphore.wait()来请求访问权限。操作完成后,调用semaphore.signal()来释放权限,允许其他线程访问队列。

dequeue方法,在从队列移除元素之前,线程会调用semaphore.wait(),操作完成后,调用semaphore.signal()释放权限。

size方法,为了获取队列的大小,也使用了信号量来确保线程安全。

控制最大并发数

这个功能多用于数据下载和处理的场景中,假设我们有一个包含多个音频文件URL的数组,但要求最多同时进行3个下载任务,这时候使用信号量就可以很好的控制这一点。

let semaphore = DispatchSemaphore(value: 3) // 信号量初始值为3,表示最多允许3个并发下载任务
let queue = DispatchQueue.global()let audioURLs = ["https://example.com/audio1.mp3", "https://example.com/audio2.mp3", "https://example.com/audio3.mp3", /* 其他URL */]for url in audioURLs {queue.async {semaphore.wait() // 请求一个下载任务的名额downloadAudio(from: url) {print("Downloaded: \(url)")semaphore.signal() // 释放下载任务的名额}}
}func downloadAudio(from url: String, completion: @escaping () -> Void) {// 模拟下载任务sleep(2) // 模拟下载时间completion()
}

信号量初始化时设置计数值为3,这意味着同时最多有3个线程可以访问资源(即开始下载)。

wait()方法,当一个线程要开始下载任务时,它需要调用wait()来减少信号量的计数。如果计数为0,表示已经有3个任务在运行,该线程会被阻塞,直到某个任务完成并调用signal()方法。

signal()方法,下载任务完成后,调用signal()增加信号量的计数,允许下一个被阻塞的线程开始下载。

注意事项

信号量作为一种强大的同步工具,可以帮助我们有效地管理并发任务和控制对共享资源的访问。然而,在使用信号量时,仍需注意以下几点:

避免死锁

在使用信号量时,必须确保wait()和signal()调用配对正确,如果一个线程在调用wait()后未能在适当的时候调用signal(),或者连续调用两次wait(),可能会导致其他线程永久阻塞,从而造成死锁。

合理设置信号量初始值

初始化信号量时,需要根据实际需求设定合理的初始计数值。如果初始值设置过高,可能导致资源被过度占用,设置过低则可能限制了并发性能。

性能考虑

虽然信号量时一种有效的同步机制,但在高并发场景下,频繁的信号量操作可能会带来性能开销。在某些情况下,考虑使用其他同步机制(如NSLock、DispatchGroup等)可能会更加合适。

结语

在本篇博客中,我们详细讨论了GCD信号量的工作原理及其应用场景。通过合理的使用GCD信号量,可以有效地管理并发任务,提升程序的性能和稳定性。然而,我们在使用信号量时需要注意其潜在的挑战,以避免引发性能问题或同步错误。

相关文章:

  • 内网与外网的区别
  • 【北京迅为】《STM32MP157开发板使用手册》- 第二十章 Trusted Firmware-A 移植+第二十一章 U-Boot移植
  • HarmonyOS开发实战( Beta5.0)自定义装饰器实践规范
  • 掌握Python自动化:探索keymousego库的无限可能!
  • Oracle OCP认证值得考吗? 需要门槛吗?
  • 【软件设计师真题】下午题第四大题---算法设计
  • 高基数 GroupBy 在 SLS SQL 中的查询加速
  • linux-进程管理-守护进程(Daemon)
  • 讯飞语音转文字怎么样?试试这4款工具吧!
  • 动态规划解决LCS问题
  • ElasticSearch底层原理解析
  • ESXI8.0 vsphere vcenter 多网卡多网段配置
  • OpenHarmony开发实战:动画样式(JS),2024年最新自学HarmonyOS鸿蒙
  • 三菱伺服电机抱闸(刹车)的用法
  • 研1日记9
  • HTML-表单
  • JavaScript标准库系列——Math对象和Date对象(二)
  • Java小白进阶笔记(3)-初级面向对象
  • js递归,无限分级树形折叠菜单
  • npx命令介绍
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 设计模式(12)迭代器模式(讲解+应用)
  • 实现菜单下拉伸展折叠效果demo
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 微信小程序开发问题汇总
  • 项目实战-Api的解决方案
  • 延迟脚本的方式
  • 异常机制详解
  • 你对linux中grep命令知道多少?
  • scrapy中间件源码分析及常用中间件大全
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​决定德拉瓦州地区版图的关键历史事件
  • (11)MATLAB PCA+SVM 人脸识别
  • (C语言)二分查找 超详细
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (南京观海微电子)——I3C协议介绍
  • (排序详解之 堆排序)
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)人的集合论——移山之道
  • (转载)深入super,看Python如何解决钻石继承难题
  • (自适应手机端)响应式服装服饰外贸企业网站模板
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .Net CoreRabbitMQ消息存储可靠机制
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET学习全景图
  • /bin/bash^M: bad interpreter: No such file ordirectory
  • @component注解的分类