文档章节

漫谈多线程:GCD(一)

Chars-D
 Chars-D
发布于 2016/03/03 07:54
字数 4388
阅读 84
收藏 1
点赞 1
评论 0

导言

多线程是程序开发中非常基础的一个概念,大家在开发过程中应该或多或少用过相关的东西。同时这恰恰又是一个比较棘手的概念,一切跟多线程挂钩的东西都会变得复杂。如果使用过程中对多线程不够熟悉,很可能会埋下一些难以预料的坑。

iOS中的多线程技术主要有NSThread, GCD和NSOperation。他们的封装层次依次递增,其中:

1)NSThread封装性最差,最偏向于底层,主要基于thread使用

2)GCD是基于C的API,直接使用比较方便,主要基于task使用

3)NSOperation是基于GCD封装的NSObject对象,对于复杂的多线程项目使用比较方便,主要基于队列使用

上篇文章介绍了NSThread的用法,NSThread已经属于古董级别的东西了,欣赏一下可以,真正使用就不要麻烦他了。GCD是多线程中的新贵,比起NSThread更加强大,也更容易使用。由于GCD的东西比较多,我会分好几篇文章介绍,这篇文章主要介绍GCD中的queue相关知识。


dispatch_queue_t

使用GCD之后,你可以不用再浪费精力去关注线程,GCD会帮你管理好一切。你只需要想清楚任务的执行方法(同步还是异步)和队列的运行方式(串行还是并行)即可。

任务是一个比较抽象的概念,表示一段用来执行的代码,他对应到代码里就是一个block或者一个函数。

队列分为串行队列和并行队列:

1)串行队列一次只能执行一个任务。只有一个任务执行完成之后,下一个任务才能执行,主线程就是一个串行的队列。

2)并行队列可以同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。

队列对应到代码里是一个dispatch_queue_t对象:

dispatch_queue_t queue;

对象就有内存。跟普通OC对象类似,我们可以用dispatch_retain()和dispatch_release()对其进行内存管理,当一个任务加入到一个queue中的时候,任务会retain这个queue,直到任务执行完成才会release。

值得高兴的是,iOS6之后,dispatch对象已经支持ARC,所以在ARC工程之下,我们可以不用担心他的内存,想怎么玩就怎么玩。

要申明一个dispatch的属性。一般情况下我们只需要用strong即可。

@property (nonatomic, strong) dispatch_queue_t queue;

如果你是写一个framework,framework的使用者的SDK有可能还是古董级的iOS6之前。那么你需要根据OS_OBJECT_USE_OBJC做一个判断是使用strong还是assign。(一般github上的优秀第三方库都会这么做)

#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong) dispatch_queue_t queue;
#else
@property (nonatomic, assign) dispatch_queue_t queue;
#endif


async

GCD中有2个异步的API

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

他们都是将一个任务提交到queue中,提交之后立即返回,不等待任务的的执行。提交之后,系统会对queue做retain操作,任务执行完成之后,queue再被release。两个函数实际的功能是一样的,唯一的区别在于dispatch_async接受block作为参数,dispatch_async_f接受函数。

使用dispatch_async的时候block会被copy,在block执行完成之后block再release,由于是系统持有block,所以不用担心循环引用的问题,block里面的self不需要weak。

在dispatch_async_f中,context会作为第一个参数传给work函数。如果work不需要参数,context可以传入NULL。work参数不能传入NULL,否则可能发生无法预料的事。

异步是一个比较抽象的概念,简单的说就是将任务加入到队列中之后,立即返回,不需要等待任务的执行。语言的描述比较抽象,我们用代码加深一下对概念的理解:

NSLog(@"this is main queue, i want to throw a task to global queue");
dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(globalQueue, ^{
    // task
});
NSLog(@"this is main queue, throw task completed");

上面这段代码,会以这样的方式运行,红色表示正在执行的模块,灰色表示未执行或者已经执行完成的模块。

1)先在main queue中执行第一个NSLog

2)dispatch_async会将block提交到globalQueue中,提交成功之后立即返回

3)main queue执行第二个NSLog

4)等global queue中block前面的任务执行完成之后,block被执行。


sync

与异步相似,GCD中同步的API也是2个

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

2个API作用相同:将任务提交到queue中,任务加入queue之后不会立即返回,等待任务执行完成之后再返回。同sync类似,dispatch_sync与dispatch_sync_f唯一的区别在于dispatch_sync接收block作为参数,block被系统持有,不需要对self使用weak。dispatch_sync_f接受函数work作为参数,context作为传给work函数的第一个参数。同样,work参数也不能传入NULL,否则会发生无法预料的事。

同步表示任务加入到队列中之后不会立即返回,等待任务完成再返回。语言的描述比较抽象,我们再次用代码加深一下对概念的理解

NSLog(@"this is main queue, i want to throw a task to global queue");
dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(globalQueue, ^{
    // task
});
NSLog(@"this is main queue, throw task completed");

我们来看看代码的运行方式:

1)先在main queue中执行第一个NSLog

2)dispatch_sync会将block提交到global queue中,等待block的执行

3)global queue中block前面的任务执行完成之后,block执行

4)block执行完成之后,dispatch_sync返回

5)dispatch_sync之后的代码执行

由于dispatch_sync需要等待block被执行,这就非常容易发生死锁。如果一个串行队列,使用dispatch_sync提交block到自己队列中,就会发生死锁。

dispatch_queue_t queue = dispatch_queue_create("com.liancheng.serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    // 到达串行队列   
    dispatch_sync(queue, ^{     //发生死锁   
    });
});

dispatch_sync的代码执行如图所示

dispatch_sync需要等待block执行完成,同时由于队列串行,block的执行需要等待前面的任务,也就是dispatch_sync执行完成。两者互相等待,永远也不会执行完成,死锁就这样发生了。

从这里看发生死锁需要2个条件:

1)代码运行的当前队列是串行队列

2)使用sync将任务加入到自己队列中

如果queue是并行队列,或者将任务加入到其他队列中,这是不会发生死锁的。


获取队列

获取主线程队列

主线程是我们最常用的线程,GCD提供了非常简单的获取主线程队列的方法。

dispatch_queue_t dispatch_get_main_queue(void)方法不需要传入参数,直接返回主线程队列。

假设我们要在主线程更新UI:

dispatch_async(dispatch_get_main_queue(), ^{
    [self updateUI];
});

执行加入到主线程队列的block,App会调用dispatch_main(), NSApplicationMain(),或者在主线程使用CFRunLoop。

获取全局队列

除了主线程队列,GCD提供了几个全局队列,可以直接获取使用

dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

dispatch_get_global_queue方法获取的全局队列都是并行队列,并且队列不能被修改,也就是说对全局队列调用dispatch_suspend(), dispatch_resume(), dispatch_set_context()等方法无效

1)identifier: 用以标识队列优先级,推荐用qos_class枚举作为参数,也可以使用dispatch_queue_priority_t

2)flags: 预留字段,传入任何非0的值都可能导致返回NULL

可以看到dispatch_get_global_queue根据identifier参数返回相应的全局队列。identifier推荐使用qos_class枚举

__QOS_ENUM(qos_class, unsigned int,
    QOS_CLASS_USER_INTERACTIVE
            __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x21,
    QOS_CLASS_USER_INITIATED
            __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x19,
    QOS_CLASS_DEFAULT
            __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x15,
    QOS_CLASS_UTILITY
            __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x11,
    QOS_CLASS_BACKGROUND
            __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x09,
    QOS_CLASS_UNSPECIFIED
            __QOS_CLASS_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) = 0x00,
);

这个枚举与NSThread中的NSQualityOfService类似

1)QOS_CLASS_USER_INTERACTIVE: 最高优先级,交互级别。使用这个优先级会占用几乎所有的系统CUP和I/O带宽,仅限用于交互的UI操作,比如处理点击事件,绘制图像到屏幕上,动画等

2)QOS_CLASS_USER_INITIATED: 次高优先级,用于执行类似初始化等需要立即返回的事件

3)QOS_CLASS_DEFAULT: 默认优先级,当没有设置优先级的时候,线程默认优先级。一般情况下用的都是这个优先级

4)QOS_CLASS_UTILITY: 普通优先级,主要用于不需要立即返回的任务

5)QOS_CLASS_BACKGROUND: 后台优先级,用于用户几乎不感知的任务。

6)QOS_CLASS_UNSPECIFIED: 未知优先级,表示服务质量信息缺失

identifier除了使用qos_class枚举,也可以用dispatch_queue_priority_t作为参数。

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
typedef long dispatch_queue_priority_t;

INT16_MINtypedef long dispatch_queue_priority_t;

dispatch_queue_priority_t对应到qos_class枚举有:

- DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND

很多时候我们喜欢将0或者NULL传入作为参数

dispatch_get_global_queue(NULL, NULL)

由于NULL等于0,也就是DISPATCH_QUEUE_PRIORITY_DEFAULT,所以返回的是默认优先级。


创建队列

当无法获取到理想的队列时,我们可以自己创建队列。

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

如果未使用ARC,dispatch_queue_create创建的queue在使用结束之后需要调用dispatch_release。

1)label: 队列的名称,调试的时候可以区分其他的队列

2)attr: 队列的属性,dispatch_queue_attr_t类型。用以标识队列串行,并行,以及优先级等信息

attr参数有三种传值方式:

// 串行
#define DISPATCH_QUEUE_SERIAL NULL
// 并行
#define DISPATCH_QUEUE_CONCURRENT \
        DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
        _dispatch_queue_attr_concurrent)       
// 自定义属性值
dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class(dispatch_queue_attr_t attr, dispatch_qos_class_t qos_class, int relative_priority);

DISPATCH_QUEUE_SERIAL或者NULL,表示创建串行队列,优先级为目标队列优先级。DISPATCH_QUEUE_CONCURRENT表示创建并行队列,优先级也为目标队列优先级。

dispatch_queue_attr_make_with_qos_class函数可以创建带有优先级的dispatch_queue_attr_t对象。通过这个对象可以自定义queue的优先级。

1)attr: 传入DISPATCH_QUEUE_SERIAL、NULL或者DISPATCH_QUEUE_CONCURRENT,表示串行或者并行

2)qos_class: 传入qos_class枚举,表示优先级级别

3)relative_priority: 相对于qos_class的相对优先级,qos_class用于区分大的优先级级别,relative_priority表示大级别下的小级别。relative_priority必须大于QOS_MIN_RELATIVE_PRIORITY小于0,否则将返回NULL。从GCD源码中可以查到QOS_MIN_RELATIVE_PRIORITY等于-15

使用dispatch_queue_attr_make_with_qos_class创建队列时,需要注意,非法的参数可能导致dispatch_queue_attr_make_with_qos_class返回NULL,dispatch_queue_create传入NULL会创建出串行队列。写代码过程中需要确保这是否是预期的结果。


设置目标队列

除了通过dispatch_queue_attr_make_with_qos_class设置队列的优先级之外,也可以使用设置目标队列的方法,设置队列的优先级。当队列创建时未设置优先级,队列将继承目标队列的优先级。(不过一般情况下还是推荐使用dispatch_queue_attr_make_with_qos_class设置队列的优先级)

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

调用dispatch_set_target_queue会retain新目标队列queue,release原有目标队列。设置目标队列之后,block将会在目标队列中执行。注意:当目标队列串行时,任何在目标队列中执行的block都会串行执行,无论原队列是否串行。

假设有队列A、B是并行队列,C为串行队列。A,B的目标队列均设置为C,那么A、B、C中的block在设置目标队列之后最终都会串行执行。

例:队列1并行,队列2串行

dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{    // block1
    for (int i = 0; i < 5; i ++) {
        NSLog(@"+++++");
    }
});
dispatch_async(queue1, ^{ // block2
    for (int i = 0; i < 5; i ++) {
        NSLog(@"=====");
    }
});
dispatch_async(queue2, ^{    // block3
    for (int i = 0; i < 5; i ++) {
        NSLog(@"----");
    }
});

运行一下可知block1,block2,block3并行执行

2016-02-25 15:05:20.024 TGCD[1940:99120] +++++
2016-02-25 15:05:20.024 TGCD[1940:99122] =====
2016-02-25 15:05:20.024 TGCD[1940:99121] ----
2016-02-25 15:05:20.025 TGCD[1940:99120] +++++
2016-02-25 15:05:20.025 TGCD[1940:99121] ----
2016-02-25 15:05:20.025 TGCD[1940:99122] =====
2016-02-25 15:05:20.025 TGCD[1940:99120] +++++
2016-02-25 15:05:20.025 TGCD[1940:99121] ----
2016-02-25 15:05:20.025 TGCD[1940:99122] =====
2016-02-25 15:05:20.025 TGCD[1940:99120] +++++
2016-02-25 15:05:20.025 TGCD[1940:99121] ----
2016-02-25 15:05:20.025 TGCD[1940:99122] =====
2016-02-25 15:05:20.025 TGCD[1940:99120] +++++
2016-02-25 15:05:20.025 TGCD[1940:99121] ----
2016-02-25 15:05:20.025 TGCD[1940:99122] =====

如果将队列1的目标队列设置为队列2,会发生什么情况呢?

dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue1, queue2);
dispatch_async(queue1, ^{
    for (int i = 0; i < 5; i ++) {
        NSLog(@"+++++");
    }
});
dispatch_async(queue1, ^{
    for (int i = 0; i < 5; i ++) {
        NSLog(@"=====");
    }
});
dispatch_async(queue2, ^{
    for (int i = 0; i < 5; i ++) {
        NSLog(@"----");
    }
});

block1,block2,block3变为了串行

2016-02-25 15:06:57.215 TGCD[1974:100675] +++++
2016-02-25 15:06:57.215 TGCD[1974:100675] +++++
2016-02-25 15:06:57.215 TGCD[1974:100675] +++++
2016-02-25 15:06:57.215 TGCD[1974:100675] +++++
2016-02-25 15:06:57.216 TGCD[1974:100675] +++++
2016-02-25 15:06:57.216 TGCD[1974:100675] =====
2016-02-25 15:06:57.216 TGCD[1974:100675] =====
2016-02-25 15:06:57.216 TGCD[1974:100675] =====
2016-02-25 15:06:57.216 TGCD[1974:100675] =====
2016-02-25 15:06:57.216 TGCD[1974:100675] =====
2016-02-25 15:06:57.216 TGCD[1974:100675] ----
2016-02-25 15:06:57.216 TGCD[1974:100675] ----
2016-02-25 15:06:57.216 TGCD[1974:100675] ----
2016-02-25 15:06:57.217 TGCD[1974:100675] ----
2016-02-25 15:06:57.217 TGCD[1974:100675] ----

注意不要循环设置目标队列,如A的目标队列为B,B的目标队列为A。这将会导致无法预知的错误


延时

GCD中有2个延时的API

dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);

一定时间之后将block加入到queue中。when用于表示时间,如果传入DISPATCH_TIME_NOW会等同于dispatch_async。另外不允许传入DISPATCH_TIME_FOREVER,这会永远阻塞线程。

通前面其他方法类似。dispatch_after接收block作为参数,系统持有block,block中self不需要weak。dispatch_after_f接收work函数作为参数,context作为work函数的第一个参数。

需要注意的是这里的延时是不精确的,因为加入队列不一定会立即执行。延时1s可能会1.5s甚至2s之后才会执行。


dispatch_barrier

在并行队列中,有的时候我们需要让某个任务单独执行,也就是他执行的时候不允许其他任务执行。这时候dispatch_barrier就派上了用场。

使用dispatch_barrier将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完成。

dispatch_barrier在GCD中有4个API

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

如果API在串行队列中调用,将等同于dispatch_async、dispatch_async_f、dispatch_sync、dispatch_sync_f,不会有任何影响。

dispatch_barrier最典型的使用场景是读写问题,NSMutableDictionary在多个线程中如果同时写入,或者一个线程写入一个线程读取,会发生无法预料的错误。但是他可以在多个线程中同时读取。如果多个线程同时使用同一个NSMutableDictionary。怎样才能保护NSMutableDictionary不发生意外呢?

- (void)setObject:(id)anObject forKey:(id
)aKey{
    dispatch_barrier_async(self.concurrentQueue, ^{
        [self.mutableDictionary setObject:anObject forKey:aKey];
    });
}
- (id)objectForKey:(id)aKey{
    __block id object = nil;    dispatch_sync(self.concurrentQueue, ^{
        object = [self.mutableDictionary objectForKey:aKey];
    });    return  object;
}

当NSMutableDictionary写入的时候,我们使用dispatch_barrier_async,让其单独执行写入操作,不允许其他写入操作或者读取操作同时执行。当读取的时候,我们只需要直接使用dispatch_sync,让其正常读取即可。这样就可以保证写入时不被打扰,读取时可以多个线程同时进行


set_specific & get_specific

有时候我们需要将某些东西关联到队列上,比如我们想在某个队列上存一个东西,或者我们想区分2个队列。GCD提供了dispatch_queue_set_specific方法,通过key,将context关联到queue上

void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);

1)queue:需要关联的queue,不允许传入NULL

2)key:唯一的关键字

3)context:要关联的内容,可以为NULL

4)destructor:释放context的函数,当新的context被设置时,destructor会被调用

有存就有取,将context关联到queue上之后,可以通过dispatch_queue_get_specific或者dispatch_get_specific方法将值取出来。

void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);

1)dispatch_queue_get_specific: 根据queue和key取出context,queue参数不能传入全局队列

2)dispatch_get_specific: 根据唯一的key取出当前queue的context。如果当前queue没有key对应的context,则去queue的target queue取,取不着返回NULL,如果对全局队列取,也会返回NULL

iOS 6之后dispatch_get_current_queue()被废弃(废弃的原因这里不多解释,如果想了解可以看这里),如果我们需要区分不同的queue,可以使用set_specific方法。根据对应的key是否有值来区分。


END

queue相关的内容就介绍到这里,GCD的东西挺多,其他东西之后如果有时间我会慢慢介绍,敬请期待


本文转载自:http://www.cocoachina.com/ios/20160225/15422.html

共有 人打赏支持
Chars-D

Chars-D

粉丝 18
博文 69
码字总数 118270
作品 15
广州
程序员
iOS 大型项目开发漫谈

作者:CrespoXiao 授权本站转载。 标题有些吓人请不要害怕,不过这确实不是扫盲贴,需要一定的iOS开发基础。在我多年的码农生涯中绝大部分时间都是做的小项目,大一些的可能也就是百万行代码...

hejunbinlan ⋅ 2015/09/09 ⋅ 0

iOS GCD~performSelector、dispatch_once、NSOperation总结

//联系人:石虎QQ:1224614774昵称:嗡嘛呢叭咪哄 一、使用GCD 替代 performSelector 系列方法 NSObject 的 performSelector 系列方法有很多限制。传给要执行的方法的参数的数量是有限制的,也没...

石虎132 ⋅ 2017/12/05 ⋅ 0

iOS多线程开发之深入GCD

iOS多线程开发之深入GCD 一、前言 在以前的一些系列博客中,对iOS中线程的管理做了总结,其中涵盖了GCD的相关基础知识:http://my.oschina.net/u/2340880/blog/417746。那里面将GCD的线程管理...

珲少 ⋅ 2015/08/11 ⋅ 0

iOS多线程编程之三——GCD的应用

iOS多线程编程之三——GCD的应用 一、引言 在软件开发中使用多线程可以大大的提升用户体验度,增加工作效率。iOS系统中提供了多种分线程编程的方法,在前两篇博客都有提及: NSThread类进行多...

珲少 ⋅ 2015/05/21 ⋅ 0

【iOS】多线程NSOperation

NSOperation是苹果封装的一套多线程的东西,不像GCD是纯C语言的,这个是OC的。但相比较之下GCD会更快一些,但本质上NSOPeration是多GDC的封装。 一、NSOperation与GCD的比较 GCD是基于c的底层...

xn4545945 ⋅ 2014/07/28 ⋅ 0

多线程的底层实现机制

1.多线程的底层实现 (1)首先回答什么是线程 1个进程要想执行任务,必须得有线程.线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行 (2)什么是多线程 1个进程中可以开...

万能的匹诺曹 ⋅ 2016/06/22 ⋅ 0

iOS OS X 和 iOS 中的多线程技术-4 (GCD)

//联系人:石虎QQ:1224614774昵称:嗡嘛呢叭咪哄 一、GCD GCD(Grand Central Dispatch)是 Apple 公司为了提高 OS X 和 iOS 系统在多核处理器上运行并行代码的能力而开发的一系列相关技术,它...

石虎132 ⋅ 2017/12/03 ⋅ 0

iOS进阶学习

iOS 内存探秘 本文深入浅出的介绍了 iOS 系统的内存机制以及开发者所需要注意的问题 GMTC 上分享滴滴出行 iOS 端瘦身实践的 Slides 滴滴出行 iOS 端瘦身实践 iOS之widget开发(Today Extensio...

掘金官方 ⋅ 2017/12/26 ⋅ 0

精华阅读第7期|程序员职业人生规划的三点建议

不久前,在中关村创新工场有一场为广大程序员同学做的职业规划分享,优才学院的 CEO 伍星和优伯立信的 CEO 罗飞给大家分享很多关于职业规划的东西。技术到底是不是吃青春饭?在职业规划时,是...

OneAPM蓝海讯通 ⋅ 2016/03/08 ⋅ 0

iOS OS X 和 iOS 中的多线程技术-1

//联系人:石虎QQ:1224614774昵称:嗡嘛呢叭咪哄 一、概念 多线程的目的是,通过并发执行提高 CPU 的使用效率,进而提供程序运行效率。 OS X 和 iOS 是多线程操作系统,它们追随 UNIX 系统使用...

石虎132 ⋅ 2017/11/21 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Thrift RPC实战(二) Thrift 网络服务模型

TServer类层次体系 TSimpleServer/TThreadPoolServer是阻塞服务模型 TNonblockingServer/THsHaServer/TThreadedSelectotServer是非阻塞服务模型(NIO) 1 TServer抽象类的定义 内部静态类Args的...

lemonLove ⋅ 15分钟前 ⋅ 0

vim命令用法

第五章 vim命令 vim和vi几乎是一样的,唯一的区别就是当编辑一个文本时,使用vi不会显示颜色,而使用vim会显示颜色。 vim有三个模式:一般模式,编辑模式,命令模式。 系统最小化安装时没有安...

弓正 ⋅ 16分钟前 ⋅ 0

MyBatis源码解读之配置

1. 目的 本文主要介绍MyBatis配置文件解析,通过源码解读mybatis-config.xml(官方默认命名)、Mapper.xml 与Java对象的映射。 2. MyBatis结构 查看大图 MyBatis结构图,原图实在太模糊了,所以...

无忌 ⋅ 20分钟前 ⋅ 0

Ignite的jdbc与网格的连接方式的查询性能对比

环境: 数据量100万 Ignite2.5 Windows10 8g jdbc方式连接 import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; i......

仔仔1993 ⋅ 35分钟前 ⋅ 0

收集自网络的wordpress 分页导航的代码教程(全网最全版)

wordpress 分页导航是用来切换文章的一个功能,添加了 wordpress 分页导航后,用户即可自由到达指定的页面数浏览分类文章,而这样的一个很简单功能却有很多朋友在用插件:WP-PageNavi,插件的...

Rhymo-Wu ⋅ 50分钟前 ⋅ 0

微服务 WildFly Swarm 入门

Hello World 就像前面章节中的其他框架一样,我们希望添加一些基本的 Hello-world 功能,然后在其上逐步添加更多的功能。让我们从在我们的项目中创建一个 HolaResources 开始。您可以使用您的...

woshixin ⋅ 57分钟前 ⋅ 0

Maven的安装和Eclipse的配置

1. 下载Maven 下载地址 2. 解压压缩包,放到自己习惯的硬盘中 此处我将其放到了 D:\Tools 目录下。 3. 配置环境变量 右键此电脑 -> 属性 -> 高级系统设置 -> 环境变量。 在系统变量中新建,变...

影狼 ⋅ 今天 ⋅ 0

python pip使用国内镜像的方法

国内源 清华:https://pypi.tuna.tsinghua.edu.cn/simple 阿里云:http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 华中理工大学:http://......

良言 ⋅ 今天 ⋅ 0

对于url变化的spa应该如何使用微信jssdk

使用vue单页面碰上微信jssdk config验证失败的坑。第一次成功 之后切换页面全部失败,找到了解决方法,第一次验证成功后保存验证信息 切换页面时验证信息直接拿来用,加一个wx.error() 失败时...

孙冠峰 ⋅ 今天 ⋅ 0

Spring Cloud Gateway 一般集成

SCF发布,带来很多新东西,不过少了点教程,打开方式又和以前的不一样,比如这个SCG,压根就没有入门指导,所以这里写一个,以备后用。 一、集成 pom.xml <dependency> <groupI...

kut ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部