iOS与多线程(一) —— GCD中的信号量及几个重要函数

版本记录

版本号 时间
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中的信号量

后记

未完,待续~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容