线程、锁搬砖iOS开发实用技术

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

2017-08-16  本文已影响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中的信号量有三种操作函数:

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

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);

信号量的一种形象比喻

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

几个简单的例子

例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中的信号量

后记

未完,待续~~~

上一篇下一篇

猜你喜欢

热点阅读