美文网首页线程、锁搬砖iOS开发实用技术
ios并发机制(一) —— GCD中的信号量及几个重要函数

ios并发机制(一) —— GCD中的信号量及几个重要函数

作者: 刀客传奇 | 来源:发表于2017-08-16 15:32 被阅读862次

    版本记录

    版本号 时间
    V1.0 2017.08.15

    前言

    信号量机制是多线程通信中的比较重要的一部分,对于NSOperation可以设置并发数,但是对于GCD就不能设置并发数了,那么就只能靠信号量机制了。接下来这几篇就会详细的说一下并发机制。

    基本概念

    信号量

    信号量其实就是一个整数值并具有一个初始化的值,有关信号量有两个操作:

    • 信号通知
    • 信号等待

    这里我们需要注意:

    • 当信号量被信号通知时,其计数会被增加。
    • 当线程在信号量等待时,线程会阻塞,计数会减少。

    GCD中的信号量

    信号量控制互斥的原理

    信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是PV操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行是,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,知道信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1, 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0,次数进程2咱有资源,排他访问资源,这就是信号量来控制互斥的原理。

    总的来说,信号量为0时就阻塞线程,大于0就不会阻塞,通过改变信号量的值控制线程的阻塞,达到线程的同步。

    三种重要的函数

    GCD中的信号量有三种操作函数:

    • dispatch_semaphore_create: 创建一个信号量,具有整形的数值,即为信号的总量。
    • dispatch_semaphore_signal
      • 返回值为long类型,当返回值为0时,表示当前并没有线程等待其处理的信号量,其处理的信号总量增加1。
      • 当返回值不为0时,表示其当前有一个或者多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级的时候,唤醒优先级最高的线程,否则随机唤醒)。
    • dispatch_semaphore_wait
      • 等待信号,具体操作是首先判断信号量desema是否大于0,如果大于0就减掉1个信号,往下执行;
      • 如果等于0函数就阻塞该线程等待timeout(注意timeout类型为dispatch_time_t)时,其所处线程自动执行其后的语句。

    下面我们就详细的说一下这几个函数。

    1. dispatch_semaphore_create
    
    /*!
     * @function dispatch_semaphore_create
     *
     * @abstract
     * // 创建具有初始值的新计数信号量。
     * Creates new counting semaphore with an initial value.
     *
     * @discussion
     * Passing zero for the value is useful for when two threads need to reconcile
     * the completion of a particular event. Passing a value greater than zero is
     * useful for managing a finite pool of resources, where the pool size is equal
     * to the value.
     * //如果两个线程需要调整特定事件的完成,则给该值传递0。 传递大于零的值对于管理有限的资源池很有用,其中池大小等于该值。
     *
     * @param value
     * The starting value for the semaphore. Passing a value less than zero will
     * cause NULL to be returned.
     * //信号的初始值,传递小于0的值会返回NULL
     * 
     * @result
     * The newly created semaphore, or NULL on failure.
     * //返回新创建的信号量,或者失败时返回NULL
     */
    __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
    DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
    DISPATCH_NOTHROW
    
    dispatch_semaphore_t
    dispatch_semaphore_create(long value);
    
    2. dispatch_semaphore_signal
    
    /*!
     * @function dispatch_semaphore_signal
     *
     * @abstract
     * Signal (increment) a semaphore.
     *  //信号量增加
     *
     * @discussion
     * Increment the counting semaphore. If the previous value was less than zero,
     * this function wakes a waiting thread before returning.
     *//增加计数信号量。 如果以前的值小于零,则此函数在返回之前唤醒等待线程。
     *
     * @param dsema The counting semaphore.
     * The result of passing NULL in this parameter is undefined.
     *// 该参数传递NULL时,返回的结果未定义。
     *
     * @result
     * This function returns non-zero if a thread is woken. Otherwise, zero is
     * returned.
     *//如果线程被唤醒,该函数返回的是个非0数,否则,返回的是0
     */
    __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
    DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
    
    long
    dispatch_semaphore_signal(dispatch_semaphore_t dsema);
    
    3. dispatch_semaphore_wait
    
    /*!
     * @function dispatch_semaphore_wait
     *
     * @abstract
     * Wait (decrement) for a semaphore.
     *//等待(递减)一个信号量
     *
     * @discussion
     * Decrement the counting semaphore. If the resulting value is less than zero,
     * this function waits for a signal to occur before returning.
     *//减少计数信号量。 如果结果值小于零,则此函数在返回之前等待信号发生。
     *
     * @param dsema
     * The semaphore. The result of passing NULL in this parameter is undefined.
     *//信号量,给这个参数传递NULL的结果没有定义。
     *
     * @param timeout
     * When to timeout (see dispatch_time). As a convenience, there are the
     * DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
     *//超时(请参阅dispatch_time)的时候, 为方便起见,有DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量。
     *
     * @result
     * Returns zero on success, or non-zero if the timeout occurred.
     *//成功后返回0,超时发生的时候返回非0数。
     */
    __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
    DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
    
    long
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    

    信号量的一种形象比喻

    下面就以停车做例子说明信号量这几个函数的使用。

    • 停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。

    • 信号量的值就相当于剩余车位的数目,
      dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了dispatch_semaphore_create(long value)

    • 调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;

    • 当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

    几个简单的例子

    例1

    下面看一个简单的使用例子。

    #import "JJGCDSemaphoreVC.h"
    
    @interface JJGCDSemaphoreVC ()
    
    @end
    
    @implementation JJGCDSemaphoreVC
    
    #pragma mark - OVerride Base Function
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor whiteColor];
        
        //创建一个为1信号量的信号
        // 打印输出:<OS_dispatch_semaphore: semaphore[0x174099b40] = { xrefcnt = 0x1, refcnt = 0x1, port = 0x0, value = 1, orig = 1 }>
        dispatch_semaphore_t signal = dispatch_semaphore_create(1);
        
        __block long x = 0;
        NSLog(@"0_x:%ld",x);
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            NSLog(@"waiting");
            
            //此时信号量为0 对signal增加1 信号量变为1,
            x = dispatch_semaphore_signal(signal);
            NSLog(@"1_x:%ld",x);
            
            sleep(2);
            NSLog(@"waking");
            
            x = dispatch_semaphore_signal(signal);
            NSLog(@"2_x:%ld",x);
        });
        
        //此时信号量为1 所以执行下边,对signal减掉1,然后信号量为0
        x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
        NSLog(@"3_x:%ld",x);
        
        //此时信号量为0,永远等待,在等待的时候执行block了,在等待block时候block内对信号量增加了1,然后开始执行下边,并且信号量再次减掉1 变为0
        x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
        NSLog(@"wait 2");
        NSLog(@"4_x:%ld",x);
        
        //此时信号量为0,永远等待,在等待的时候执行block了,在等待block时候block内对信号量增加了1,然后开始执行下边,并且信号量再次减掉1 变为0
        x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
        
        NSLog(@"wait 3");
        NSLog(@"5_x:%ld",x);
        
        sleep(2);
        
        x = dispatch_semaphore_signal(signal);
        NSLog(@"6_x:%ld",x);
    }
    
    @end
    

    下面看输出结果,这里要注意的是调用顺序和信号量的值。

    2017-08-16 14:46:15.126895+0800 JJOC[10085:4483826] 0_x:0
    2017-08-16 14:46:15.126978+0800 JJOC[10085:4483826] 3_x:0
    2017-08-16 14:46:16.128546+0800 JJOC[10085:4483865] waiting
    2017-08-16 14:46:16.128746+0800 JJOC[10085:4483826] wait 2
    2017-08-16 14:46:16.128803+0800 JJOC[10085:4483826] 4_x:0
    2017-08-16 14:46:16.128870+0800 JJOC[10085:4483865] 1_x:1
    2017-08-16 14:46:18.132708+0800 JJOC[10085:4483865] waking
    2017-08-16 14:46:18.132878+0800 JJOC[10085:4483826] wait 3
    2017-08-16 14:46:18.132959+0800 JJOC[10085:4483826] 5_x:0
    2017-08-16 14:46:18.133062+0800 JJOC[10085:4483865] 2_x:1
    2017-08-16 14:46:20.134107+0800 JJOC[10085:4483826] 6_x:0
    

    例2

    #import "JJGCDSemaphoreVC.h"
    
    @interface JJGCDSemaphoreVC ()
    
    @end
    
    @implementation JJGCDSemaphoreVC
    
    #pragma mark - OVerride Base Function
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor whiteColor];
        
        [self demo2];
    }
    
    #pragma mark - Object Private Function
    
    - (void)demo2
    {
        // 创建队列组
        dispatch_group_t group = dispatch_group_create();
        
        // 创建信号量,并且设置值为10
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        for (int i = 0; i < 100; i++)
        {
            // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,
            //从而semaphore-1.当循环10次后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
            NSInteger value = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"1_%ld",value);
            
            dispatch_group_async(group, queue, ^{
                NSLog(@"%i",i);
                sleep(3);
                // 每次发送信号则semaphore会+1,
                NSInteger value = dispatch_semaphore_signal(semaphore);
                NSLog(@"2_%ld",value);
            });
        }
    }
    @end
    

    下面看一部分输出结果

    2017-08-16 15:15:16.975989+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:15:19.638091+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:21.910431+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:23.324425+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:24.651183+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:26.033626+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:45.187604+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:46.250799+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:47.151037+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:48.017023+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:16:52.117765+0800 JJOC[10107:4488216] 1
    2017-08-16 15:16:52.118002+0800 JJOC[10107:4488217] 0
    2017-08-16 15:20:20.225583+0800 JJOC[10107:4488384] 2
    2017-08-16 15:20:20.225801+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:20:26.887084+0800 JJOC[10107:4488216] 2_1
    2017-08-16 15:22:02.490991+0800 JJOC[10107:4488216] 3
    2017-08-16 15:22:02.491183+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:22:05.175372+0800 JJOC[10107:4488217] 2_1
    2017-08-16 15:22:06.512384+0800 JJOC[10107:4488217] 4
    2017-08-16 15:22:06.512568+0800 JJOC[10107:4488195] 1_0
    2017-08-16 15:22:08.819089+0800 JJOC[10107:4488216] 2_1
    2017-08-16 15:22:09.809285+0800 JJOC[10107:4488216] 5
    2017-08-16 15:22:09.809472+0800 JJOC[10107:4488195] 1_0
    (lldb) 
    

    大家分析一下输出结果就可以看到线程信息的同步。

    参考文献

    1. iOS开发系列-信号量
    2. 关于dispatch_semaphore的使用
    3. 浅谈GCD中的信号量

    后记

    未完,待续~~~

    相关文章

      网友评论

      • oldmonster:例1中,打印出来的顺序是:
        2017-08-16 14:46:15.126895+0800 JJOC[10085:4483826] 0_x:0
        2017-08-16 14:46:15.126978+0800 JJOC[10085:4483826] 3_x:0
        2017-08-16 14:46:16.128546+0800 JJOC[10085:4483865] waiting
        2017-08-16 14:46:16.128746+0800 JJOC[10085:4483826] wait 2
        但在waiting之后不应该是打印1_x:这个的值么,还是说因为线程的原因,这两个打印顺序都是可以的
        程序狗:这两个顺序都可以的
        刀客传奇:@oldmonster 晚点看看回复

      本文标题:ios并发机制(一) —— GCD中的信号量及几个重要函数

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