GCD的API详解

Dispatch Queue

首先回顾一下苹果GCD的说明

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。

这句话的用源代码表示如下:

dispatch_asyn(queue, ^{
  //想要执行的任务
});

该源代码使用block语法定义想要执行的任务,通过dispatch_async函数追加赋值变量queue的“Dispatch Queue”中,仅这样就可以使指定的block在另一线程中执行。

Dispatch Queue 是什么,是执行处理的等待队列,应用程序编程人员通过dispatch_any等函数,在block中记述想要执行等处理并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序执行处理。

两种Dispatch Queue

Dispatch Queue种类 说明
Serial Dispatch Queue 等待现在执行中处理结束
Concurrent Dispatch Queue 不等待现在执行中处理

Serial Dispatch Queue: 假设把A B C D四个任务依次加入到一个Serial Dispatch Queue中,这个Serial Dispatch Queue会创建一个线程,依次执行这四个任务。

Concurrent Dispatch Queue: 假设把A B C D四个任务依次加入到一个Concurrent Dispatch Queue中,这个Concurrent Dispatch Queue会创建多个线程,并发执行这四个任务。

具体执行效果参看这个Demo

dispatch_queue_create

通过dispatch_queue_create函数可生成Dispatch Queue。

创建Serail Queue:

dispatch_queue_t mySerailQueue = dispatch_queue_create("com.river.dispatchSerailQueue", NULL)
;

第一个参数是指定Serial dispatch queue的名称。该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示。

第二个参数是生成Dispatch Queue的类型,如果是NULL生成Serial dispatch queue,指定为DISPATCH_QUEUE_CONCURRENT生成Concurrent Dispatch Queue。

创建Concurrent Queue:

dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.river.dispatchConcurrentQueue", DISPATCH_QUEUE_CONCURRENT)
    ;

dispatch_queue_create函数的返回值为表示Dispatch Queue的“dispatch_queue_t”类型。

注意:Serial Dispatch Queue只能同时执行一个追加处理。使用dispatch_queue_create可以生成多个dispatch queue。当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然一个Serial Dispatch Queue同时只能执行一个追加处理,但是如果将处理(任务)追加到4个Serial Dispatch Queue,各个Serial Dispatch Queue执行1个,即为同时执行4个处理。但是,要警惕的是,如果生成太多的Serial Dispatch Queue会消耗大量内存。为了避免多线程造成的数据竞争,可以使用Serial Dispatch Queue。

避免数据竞争

ARC下dispatch_release使用

在iOS6之前GCD未加入到ARC模式下,所以如果应用程序要支持iOS6.0之前对版本,则需要使用dispatch_release。可以做如下处理。

//当前系统支持的最小版本
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
#else                                         // iOS 5.X or earlier
    #define NEEDS_DISPATCH_RETAIN_RELEASE 1
#endif

/* implementation */
#if NEEDS_DISPATCH_RETAIN_RELEASE
    dispatch_release(self.reachabilitySerialQueue);
#endif

Main Dispatch Queue/Global Dispatch Queue

获取系统标准提供的Dispatch Queue。

Main Dispatch Queue是在主线程执行的dispatch queue, Main Dispatch Queue是一个Serail Dispatch Queue。追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。一般将用户界面更新等必需要在主线程中执行的处理追加到Main Dispatch Queue中。

Global Dispatch Queue是所有应用程序都能过使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个创建Concurrent Dispatch Queue,只要获取Global Dispatch Queue使用即可。Global Dispatch Queue有四个优先级

名称 Dispatch Queue的种类 说明
Main Dispatch Queue Serial Dispatch Queue 主线程执行
Global Dispatch Queue(High Priority) Concurrent Dispatch queue 执行优先级:高(最高)
Global Dispatch Queue(Default Priority) Concurrent Dispatch queue 执行优先级:默认
Global Dispatch Queue(Low Priority) Concurrent Dispatch queue 执行优先级:低
Global Dispatch Queue(Background Priority) Concurrent Dispatch queue 执行优先级:后台

获取Dispatch Queue方法

ispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

使用Main Dispatch Queue 和 Global Dispatch Queue

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        /**
         *  可并行处理的任务TODO
         */

        dispatch_async(dispatch_get_main_queue(), ^{
            /**
             *  主线程执行
             */
        });
    });

dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch_Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.river.myserialdispatchqueue", NULL);
    dispatch_queue_t globalDispatchQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackGround);

第一个参数:要变更执行优先级的Dispatch Queue

第二个参数:要使用的执行优先级相同的Global Dispatch Queue。

如果指定第一个参数为Main Dispatch Queue 和 Global Dispatch Queue则不知道会发生什么情况。

如果在多个Serial Dispatch Queue 中将一个Serial Dispatch Queue设置为第二个参数,那么原本应该并行执行的Serial Dispatch Queue,在目标Serial Dispatch Queue上只能串行执行。可参考Demo

dispatch_after

dispatch_after表示在指定的时间之后追加处理到Dispatch Queue。并不是指定时间后执行处理。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"等待3秒后执行");
 });

虽然在有严格时间到要求下使用时会出现问题,但在大致延迟执行处理时,该函数还是有效的。

dispatch_time函数通常用于计算相对时间,而dispatch_walltime函数用于计算绝对时间。

NSDate转成dispatch_time_t

- (dispatch_time_t) getDispatchDateByDate:(NSDate *)date{
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;

    //以当前时间(Now)为基准时间,返回实例保存的时间与当前时间(Now)的时间间隔,秒
    interval = [date timeIntervalSince1970];
    //拆分interval值,返回它的小数部分,second指向整数部分
    subsecond = modf(interval, &second);
    time.tv_sec = second;   //秒
    time.tv_nsec = subsecond *NSEC_PER_SEC; //纳秒 1秒=1000000000纳秒
    milestone = dispatch_walltime(&time, 0);
    return milestone;
}

Dispatch Group

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现。只使用一个Serial Dispatch Queue时,只要将想执行的处理全部追加到该Serial Dispatch Queue中并在最后加上结束处理即可实现,但是使用Concurrent Dispatch Queue时或者同时使用多个Dispatch Queue时,源代码将会变得非常复杂。

在这种情况下使用Dispatch Group。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2000; i++) {
            NSLog(@"111111");
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2000; i++) {
            NSLog(@"2222222");
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2000; i++) {
            NSLog(@"33333");
        }
    });
    //最后执行4444
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"44444");
    });
    NSLog(@"555555");

另外,在Dispatch Group中也可以使用dispatch_group_wait

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2000; i++) {
            NSLog(@"111111");
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2000; i++) {
            NSLog(@"2222222");
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2000; i++) {
            NSLog(@"33333");
        }
    });
    //DISPATCH_TIME_FOREVER永久等待
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"44444");

dispatch_group_wait函数的第二个参数指定为等待的时间,它属于dispatch_time_t类型的值。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
        /**
         *属于dispatch group的任务全部执行完毕
         */
    }else {
        /**
         *属于dispatch group的任务没有全部执行完毕
         */
    }

dispatch_group_wait等待的意思:一旦调用dispatch_group_wait,该函数就处于调用状态而不返回,执行dispatch_group_wait函数的线程停止。知道dispatch_group_wait返回结果。

long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);

注意推荐使用dispatch_group_notify;

dispatch_barrier_async

dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上对并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函数追加的处理执行完毕之后,Concurrent Dispatch Queue才会恢复一般动作。

 dispatch_async(queue, ^{
        //去文件
    });
    dispatch_async(queue, ^{
        //去文件
    });
    dispatch_async(queue, ^{
        //去文件
    });
    //使用dispatch_barrier_async避免数据竞争
    dispatch_barrier_async(queue, ^{
        //写文件
    });
    dispatch_async(queue, ^{
        //去文件
    });
    dispatch_async(queue, ^{
        //去文件
    });

    dispatch_async(queue, ^{
        //去文件
    });

dispatch_barrier_async执行过程

dispatch_barrier_async图解

dispatch_sync

dispatch_async函数中的async意味着非同步。就是将指定的Block非同步地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待。

dispatch_sync函数意味着同步,也就是将指定的Block同步追加到Dispatch Queue中,再追加结束之前dispatch_sync函数会一直等待。一旦调用dispatch_sync函数,那么指定的处理执行结束之前,该函数不会返回。

dispatch_sync函数容易引起问题,死锁

dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"11111");
    });

该段代码在主线程中执行指定的Block,并等待其执行结束。而其实主线程正在执行此段代码,所以无法执行追加到Mian Dispatch Queue中的Block。

dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"11111");
        });
    });

这段代码同样会引起错误。同样下面代码也会引起错误

    dispatch_queue_t queue = dispatch_queue_create("com.river.queue", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"11111");
        });
    })

大家使用dispatch_sync时候要好好考虑。

dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到Dispatch Queue中,并等待全部处理执行结束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu", index);//并行
    });
    //最后执行
    NSLog(@"11");

对数组的快速处理:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"%zu:%@", index, [array objectAtIndex:index]);
    });

推荐dispatch_async函数非同步地执行dispatch_apply函数

  dispatch_async(queue, ^{
        dispatch_apply([array count], queue, ^(size_t index) {
            /**
             *  并行执行
             */
            NSLog(@"%zu:%@", index, [array objectAtIndex:index]);
        });

        /**
         *  等待dispatch_apply全部处理完
         */

        dispatch_async(dispatch_get_main_queue(), ^{
            /**
             *  在main dispatch queue中执行,用户界面更新等
             */
            NSLog(@"done");
        });

    });

dispatch_suspend/dispatch_resume

当追加大量处理到Dispatch Queue时,在追加处理到过程中,有时希望不执行已追加的处理。在这种情况下只要挂起Dispatch Queue即可。

dispatch_suspend 函数挂起指定的Dispatch Queue

dispatch_resume 函数恢复指定的Dispatch Queue

这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中处理在此之后暂停执行,而恢复使得这些处理继续执行。

Dispatch Semaphore

当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但有必要进行更细粒度的排他控制。

Dispatch Semaphore 是持有计数的信号,该计数是多线程编程中的计数类型信号,所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,而在Dispatch Semaphore 中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

参数表示计数的初始值。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispatch Semaphore 的计数值达到大于或等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    long result = dispatch_semaphore_wait(semaphore, time);
    if (result == 0) {
        /**
         * 由于Dispatch Semaphore 的计数值达到大于等于1
         或者待机中指定时间内,Dispatch Semaphore 的计数值
         达到大于等于1
         所以 Dispatch Semaphore 的计数值减去1
         
         可执行需要进行排他控制的处理
         */
    } else {
        /**
         *  由于Dispatch Semaphore 的计数值为 0
            因此在达到指定时间为待机。
         */
    }

dispatch_semaphore_wait函数返回0时,可安全地执行需要进行排他控制的处理。该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    /**
     * 生成Dispatch Semaphore
     * Dispatch Semaphore 的计数初始值设定为1
     * 保证可访问NSMutableArray类对象的线程同时只有一个
     */
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    NSMutableArray *array = [[NSMutableArray alloc] init];
    
    for (int i=0; i<10000; i++) {
        dispatch_async(queue, ^{
            /**
             *  等待Dispatch Semaphore
                一直等待,直到Dispatch Semaphore 的计数的值达到大于等于1
             */
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
            /**
             *  由于Dispatch Semaphore 的计数达到大于等于1
             所以Dispatch Semaphore 的计数值减去1
             dispatch_semaphore_wait函数执行返回
             
             即执行到此时Dispatch Semaphore的计数值恒为00
             
             由于可访问NSMutableArray类对象的线程数只有1个
             因此可安全的进行更新
             */
            [array addObject:[NSNumber numberWithInt:i]];
            
            /**
             * 排他控制处理结束
             所以通过dispatch_semaphore_signal函数
             将DispatchSemaphore的计数值加1
             如果有通过dispatch_semaphore_wait函数
             等待Dispatch Semaphore的计数值增加的线程
             就由最先等待的线程执行。
             */
            dispatch_semaphore_signal(semaphore);
        });
    }

dispatch_once

dispatch_once 函数保证在应用程序执行中只执行一次指定处理的API。下面这种经常出现的用来初始化的源代码可通过dispatch_oce函数简化

static int initialized = NO;
if(initialized == NO){
  initialized = YES;
}

//使用dispatch_once
static disptach_once_t pred;
dispatch_once(&pred, ^{
  //初始化
});

通过dispatch_once函数,该代码再多线程环境下也是百分之百安全。

Dispatch I/O

在读取大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。实现这一功能可使用Dispatch I/O和Dispatch Data。

通过dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按某个大笑read/write.

    dispatch_async(queue, ^{
        /**
         *  读取 0~1001字节
         */
    });
    dispatch_async(queue, ^{
        /**
         *  读取 1002~2001字节
         */
    });
    dispatch_async(queue, ^{
        /**
         *  读取 2002~3001字节
         */
    });
    dispatch_async(queue, ^{
        /**
         *  读取 3002~4001字节
         */
    });

demo

 pipe_q = dispatch_queue_create("PipeQ", NULL);
        pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
            close(fd);
        });
        
        *out_fd = fdpair[1];
        
        dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
        
        dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
            if (err == 0)
            {
                size_t len = dispatch_data_get_size(pipedata);
                if (len > 0)
                {
                    const char *bytes = NULL;
                    char *encoded;
                    uint32_t eval;
                    
                    dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                    encoded = asl_core_encode_buffer(bytes, len);
                    asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded);
                    free(encoded);
                    eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1);
                    _asl_send_message(NULL, eval, aux, NULL);
                    asl_msg_release(aux);
                    dispatch_release(md);
                }
            }
          if (done)
            {
                dispatch_semaphore_signal(sem);
                dispatch_release(pipe_channel);
                dispatch_release(pipe_q);
            }
        });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Grand Central Dispatch(GCD)概要 我的博客链接 什么是GCD? 苹果官方这么描述的:Gr...
    换个名字再说阅读 1,273评论 4 7
  • 多线程概念 线程线程指的是:1个CPU执行的CPU命令列为一条无分叉路径 多线程这种无分叉路径不止一条,存在多条即...
    我系哆啦阅读 571评论 0 5
  • 问题: Given four lists A, B, C, D of integer values, comput...
    Cloudox_阅读 126评论 0 0
  • 不会博弈论的孩子是玩不好德州扑克的. 囚徒困境 在博弈论中,含有占优战略均衡的一个著名例子是由塔克给出的“囚徒困境...
    paddy2016阅读 1,580评论 0 3
  • 分离的知识,难以解答真正的现实 一个高水平的学习者非常善于在所学的知识之间创造关联,而一个普通的学习者头脑中的知识...
    baby鹿阅读 352评论 0 0