并发编程 - GCD信号量

news2024/11/24 3:43:02

引言

在现代应用开发中,处理并发任务已经成了不可避免的挑战。在这种情况下,如何有效地管理多个线程对共享资源的访问,避免资源竞争和数据不一致的问题,成为了我们必须面对的难题。在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 = 100
        let 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 - 1
            print("剩余票数:\(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 = 100
        let 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 - 1
            print("剩余票数:\(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 Foundation

class 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.count
        semaphore.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信号量,可以有效地管理并发任务,提升程序的性能和稳定性。然而,我们在使用信号量时需要注意其潜在的挑战,以避免引发性能问题或同步错误。

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

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

相关文章

OpenCV结构分析与形状描述符(21)计算包围给定点集的最小面积三角形函数minEnclosingTriangle()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 找到一个包围二维点集的最小面积三角形&#xff0c;并返回其面积。 该函数找到一个包围给定的二维点集的最小面积三角形&#xff0c;并返回其面…

【HarmonyOS】云开发-云数据库(二)

背景 书接上回&#xff0c;实现了云侧和端侧的云数据库创建、更新、修改等操作。这篇文章实现调用云函数对云数据库进行增删改查。 CloudProgram 项目配置 新建函数 在cloudfunctions目录下点击右键&#xff0c;选择新建Cloud Function&#xff0c;输入query-student-functi…

使用OpenCV进行模糊检测(拉普拉斯算子)

参考&#xff1a; 使用OpenCV进行模糊检测&#xff08;拉普拉斯算子&#xff09; 代码&#xff1a; # import the necessary packages from imutils import paths import argparse import cv2 import osdef variance_of_laplacian(image):# compute the Laplacian of the ima…

聚观早报 | 极越07正式上市;宝骏云海正式上市

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 9月12日消息 极越07正式上市 宝骏云海正式上市 滴滴包车全国上线 淘宝Apple Vision Pro版重大更新 OpenAI将发布…

【中秋月饼系列】2024年立体月饼新鲜出炉----python画月饼(1)附完整代码

【中秋月饼系列】2024年立体月饼新鲜出炉 ----python画月饼&#xff08;1&#xff09;附完整代码 本文目录&#xff1a; 零、时光宝盒 一、2024年中秋节立体逼真月饼&#xff08;效果展示&#xff09; 二、Python 海龟画图主要方法 &#xff08;1&#xff09;海龟画图的主…

【Linux】:信号的保存和信号处理

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家带来信号的保存和信号处理相关代码和知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入…

SpringBoot + MySQL + MyBatis 实操示例教学

一、准备工作 1.导入相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><…

网络安全实训十(Windows提权、UAC绕过、Linux利用suid提权)

一、Windows提权 1 手动查找系统存在漏洞 使用命令查看安装的补丁信息 systeminfo wmic qfe get caption,description,hotfixid,installedon 2 自动查找系统存在漏洞 2.1 Windows Exploit Suggester 2.1.1 下载脚本 下载地址&#xff1a;https://github.com/AonCyberLabs/Wi…

【有啥问啥】深入解析3A算法:自动对焦、自动曝光与自动白平衡的原理、实现与应用

深入解析3A算法&#xff1a;自动对焦、自动曝光与自动白平衡的原理、实现与应用 在现代图像处理技术中&#xff0c;3A算法&#xff08;自动对焦、自动曝光、自动白平衡&#xff09;是数码摄像设备核心的成像控制系统&#xff0c;负责调节图像的清晰度、亮度和色彩平衡。这些算…

《深度学习》—— 神经网络基本结构

前言 深度学习是一种基于神经网络的机器学习算法&#xff0c;其核心在于构建由多层神经元组成的人工神经网络&#xff0c;这些层次能够捕捉数据中的复杂结构和抽象特征。神经网络通过调整连接各层的权重&#xff0c;从大量数据中自动学习并提取特征&#xff0c;进而实现预测或…

Aigtek功率放大器的工作状态和技术指标有哪些

功率放大器是电子电路中的重要组成部分&#xff0c;用于放大电信号的功率&#xff0c;以便驱动负载&#xff0c;如扬声器、天线或电动机。它在各种应用中都起到至关重要的作用&#xff0c;从音响系统到通信设备&#xff0c;以下是功率放大器的工作状态和技术指标的详细介绍。 工…

利用zabbix监控ogg进程(Windows平台)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Boost.pyhon 使用方法

哈哈,又是一个相当nice的技巧 boost 在使用时定义使用静态库 很完整的功能 方法实现如上,很多时候写python脚本直接执行还是最容易的,编译打包还是比较麻烦,内置解释器到QT C的代码中 加载python脚本时,从python 脚本中获取值&#xff0c;在C 中进行计算使用 在python 和 C 的交…

OLED显示屏应用(STM32)

一、接线 OLED的四针脚对应接法如下图 GND——GND 3.3V——3.3V SCL——PB8 SDA——PB9 二、OLED.c代码介绍 #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ //OLED时钟线 //GPIOB8接时钟线接口 //定义一个函数&#xff0c;函数的参数为…

【JavaScript】LeetCode:31-35

文章目录 31 反转链表32 回文链表33 环形链表34 环形链表Ⅱ35 合并两个有序链表 31 反转链表 初始化&#xff1a;cur head&#xff0c;pre null。pre和cur一起向前移。由于反转链表时&#xff0c;cur.next指向pre&#xff0c;导致cur在下次循环中就找不到了原来的cur.next&am…

牛客思维题———进制(简单)

C-小红的双好数&#xff08;easy&#xff09;_牛客周赛 Round 57 (nowcoder.com) 思路&#xff1a; 任何一个数n可以表示为n进制&#xff0c;且值为1 特判1 2 即可 代码&#xff1a; #include<bits/stdc.h> #define int long long using namespace std;#define IOS i…

Java设计模式—面向对象设计原则(一) ----->开闭原则OCP(完整详解,附有代码+案例)

3.1开闭原则 对扩展开放&#xff0c;对修改关闭。在程序需要进行拓展的时候&#xff0c;不能去修改原有的代码&#xff0c;实现一个热插拔的效果。简言之&#xff0c;是为了使程序的扩展性好&#xff0c;易于维护和升级。想要达到这样的效果&#xff0c;我们需要使用接口和抽象…

【黑金系】金融UI/UX体验设计师面试作品集 Figma源文件分享

在数字金融时代&#xff0c;UI/UX体验设计师扮演着至关重要的角色。他们不仅塑造着产品的界面&#xff0c;更引领着用户的使用体验。我们的面试作品集&#xff0c;正是这样一部展现金融UI/UX设计魅力的宝典。 这套作品集汇聚了众多经典案例&#xff0c;每一处设计都经过精心雕…

docker部署bind9

一、部署 ## docker 部署bind9# docker run -d --name bind9 --restartalways --publish 53:53/tcp --publish 53:53/udp --publish 10000:10000/tcp --volume /data/docker/dns-server:/data --env ROOT_PASSWORDroot dhub.kubesre.xyz/sameersbn/bind:9.16.1-20200524# 建数…

高等数学精解【13】

文章目录 简化二次方程轴平移轴平移是一种简化二次方程图形表示的有用技巧一元二次方程的轴平移二元二次方程的轴平移轴平移简化二次方程定义性质计算例子一元二次方程的例子二元二次方程的例子&#xff08;圆&#xff09; 例题 轴旋转简化二次方程轴旋转的定义轴旋转的性质例题…