iOS 多线程之dispatch_semaphore(信号量)

什么是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主要作用

  1. 异步转同步
    • 众所周知并发队列中的任务,由异步线程执行的顺序是不确定的,两个任务分别由两个线程执行,很难控制哪个任务先执行完,哪个任务后执行完。但有时候确实有这样的需求:两个任务虽然是异步的,但仍需要同步执行。
//开启异步执行三个操作
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相当于加锁效果

  1. 设置最大开辟的线程数
    • 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定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. 加锁机制
    • 当你的信号设置为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 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。



作者:枫叶无处漂泊
链接:https://www.jianshu.com/p/a601534b14a0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2022-10-19 15:43  brave-sailor  阅读(726)  评论(0编辑  收藏  举报