美文网首页iOS
iOS 多线程之dispatch_semaphore(信号量)

iOS 多线程之dispatch_semaphore(信号量)

作者: nick5683 | 来源:发表于2024-05-21 16:21 被阅读0次

    什么是dispatch_semaphore(信号量)?
    以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看 门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开 车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。


    言归正传:

    dispatch_semaphore主要的三个方法

    GCD信号量机制主要涉及到以下三个函数:

    // 创建信号量,value:初始信号量数 如果小于0则会返回NULL
    dispatch_semaphore_create(long value); 
    
    // 发送信号量是的信号量+1
    dispatch_semaphore_signal(dispatch_semaphore_t deem);
    
    //当信号量为0时候,会阻塞当前线程 参数(信号量,等待时间)
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 
    

    dispatch_semaphore主要作用

    • 异步转同步
      众所周知并发队列中的任务,由异步线程执行的顺序是不确定的,两个任务分别由两个线程执行,很难控制哪个任务先执行完,哪个任务后执行完。但有时候确实有这样的需求:两个任务虽然是异步的,但仍需要同步执行。
    //开启异步执行三个操作
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: A");
    });
        
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: B");
    });
        
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: C");
    });
    

    执行多次结果表明:A,B,C都是异步的,谁前谁后无法确定。所以可以使用dispatch_semaphore,让异步变同步,让上面的执行顺序是A,B,C.

    //信号量初始化必须大于等于0, 因为dispatch_semaphore_wait 执行的是-1操作。
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //创建异步队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: A");
        //让信号量+1
        dispatch_semaphore_signal(semaphore);
    });
    
    //当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程.(相当于加锁)
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: B");
        //让信号量+1(相当于解锁)
        dispatch_semaphore_signal(semaphore);
    });
    
    //当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        
        sleep(1);
        NSLog(@"执行任务: C");
        dispatch_semaphore_signal(semaphore);
    });
    

    多次运行的结果都是A,B,C顺序执行,让A,B,C异步执行变成同步执行,dispatch_semaphore相当于加锁效果

    • 设置最大开辟的线程数
      我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。
    //设置最大开辟的线程数为3
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
        //创建一个并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        //开启的是10个异步的操作,通过信号量,让10个异步的最多3个m,剩下的同步等待
        for (NSInteger i = 0; i < 10; i++) {
            
            dispatch_async(queue, ^{
                //当信号量为0时候,阻塞当前线程
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                NSLog(@"执行任务 %ld", i);
                sleep(1);
                NSLog(@"完成当前任务 %ld", i);
                //释放信号
                dispatch_semaphore_signal(semaphore);
            });
        }
    

    执行结果:

    执行任务 0
    执行任务 2
    执行任务 1
    完成当前任务 2
    完成当前任务 1
    完成当前任务 0
    执行任务 3
    执行任务 4
    执行任务 5
    完成当前任务 3
    完成当前任务 5
    完成当前任务 4
    执行任务 6
    执行任务 8
    执行任务 7
    完成当前任务 7
    完成当前任务 8
    完成当前任务 6
    执行任务 9
    完成当前任务 9
    

    执行结果表明:最开始执行3个异步操作,剩下的同步等待,只有释放出来才可以剩下的操作才可以进行异步操作

    • 加锁机制
      当你的信号设置为1的时候就相当于加锁
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
        
    semaphoreLock = dispatch_semaphore_create(1);
    self.ticketCount = 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.ticketCount > 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);
        }    
    }
    

    可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。

    原文链接:枫叶无处漂泊
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

        本文标题:iOS 多线程之dispatch_semaphore(信号量)

        本文链接:https://www.haomeiwen.com/subject/qqhrzdtx.html