背景
- 我们一直玩着线程,那多线程原理?
- GCD使用及坑点?
- GCD原理-libdispatch-队列-线程-同步/异步-栅栏函数
- 为解决多线程资源抢夺的问题,怎么加锁呢?
- 大民哥带你探索多线程、GCD以及锁的原理
准备
- 可编译的
objc4
工程 libdispatch
源码swift-corelibs-foundation
Swift的foundation源码foundation
源码
线程
一般开辟线程内存最小为16KB
,默认开辟线程内存为512KB
,其中主线程默认1MB
,对于1GB
来说最多可开辟64 * 1024
个线程
线程和进程定义
- 进程
是指在系统中正在运⾏的⼀个应⽤程序
每个进程之间是独⽴的,每个进程均运⾏在其专⽤的且受保护的内存空间内
通过“活动监视器”可以查看 Mac 系统中所开启的进程 - 线程
是进程的基本执⾏单元,⼀个进程的所有任务都在线程中执⾏
进程要想执⾏任务,必须得有线程,进程⾄少要有⼀条线程
程序启动会默认开启⼀条线程,这条线程被称为主线程或 UI 线程 地址空间
:同⼀进程的线程共享本进程的地址空间,⽽进程之间则是独⽴的地址空间。资源拥有
:同⼀进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独⽴的。
1: ⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进
程都死掉。所以多进程要⽐多线程健壮。
2: 进程切换时,消耗的资源⼤,效率⾼。所以涉及到频繁的切换时,使⽤线程要好于进
程。同样如果要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程不能⽤进程
3: 执⾏过程:每个独⽴的进程有⼀个程序运⾏的⼊⼝、顺序执⾏序列和程序⼊⼝。但是
线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。
4: 线程是处理器调度的基本单位,但是进程不是。
5: 线程没有地址空间,线程包含在进程地址空间中
多线程优缺点
(操作系统书籍,“看书”)
- 优点
1,能适当提⾼程序的执⾏效率
2,能适当提⾼资源的利⽤率(CPU,内存)
3,线程上的任务执⾏完成后,线程会⾃动销毁 - 缺点
1,开启线程需要占⽤⼀定的内存空间(默认情况下,每⼀个线程都占 512 KB)
2, 如果开启⼤量的线程,会占⽤⼤量的内存空间,降低程序的性能
3,线程越多,CPU 在调⽤线程上的开销就越⼤
4,程序设计更加复杂,⽐如线程间的通信、多线程的数据共享
线程调度
时间⽚的概念:CPU在多个任务直接进⾏快速的切换,这个时间间隔就是时间⽚
- (单核CPU)
同⼀时间,CPU 只能处理 1 个线程
- 换⾔之,
同⼀时间只有 1 个线程在执⾏
- 多线程同时执⾏:
- 是
CPU 快速的在多个线程之间的切换
CPU 调度线程的时间⾜够快,就造成了多线程的“同时”执⾏的效果
- 如果线程数⾮常多
- CPU 会在 N 个线程之间切换,消耗⼤量的 CPU 资源
- 每个线程被调度的次数会降低,线程的执⾏效率降低
线程是执行任务的基本单元
(多核是解决高并发的根本,但iOS来说一直是单核)
同步/异步
- 能否开辟线程,同步不能开辟线程,异步则可
- 任务的回调是否具备异步性和同步性
队列
-
串行和并发
-
串型队列:FIFO,
dispatch_queue_t serial = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
-
并发队列:CPU的调度,
dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
#pragma mark - 队列函数的应用
- (void)textDemo2{
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
// NSLog(@"4");
});
NSLog(@"5");
// 1 5 2 死锁
//
}
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
死锁
dispatch_sync_f_slow
案例
堵塞的是整个dispatch_async();,需要外层的dispatch_async()走完,块与块的循环死锁
主线程因为你同步函数的原因等着先执⾏任务
主队列等着主线程的任务执⾏完毕再执⾏⾃⼰的任务
主队列和主线程相互等待会造成死锁
多线程原理
线程生命周期:
线程池
- 1,先判断线程池的线程是否都有任务,如果线程池工作队列饱满都处于执行状态,就
交给饱和策略去处理
(直接丢弃任务,淘汰超时任务,报异常(超负载), 任务退回到调度者),
饱和策略
这四种拒绝策略均实现的RejectedExecutionHandler接⼝:
- AbortPolicy 直接抛出RejectedExecutionExeception异常来阻⽌系统正常运⾏
- CallerRunsPolicy 将任务回退到调⽤者
- DisOldestPolicy 丢掉等待最久的任务
- DisCardPolicy 直接丢弃任务
任务执行速度影响
- CPU调度速度
- 任务复杂度
- 任务优先级
- 线程状态
优先级翻转
- IO密集型 频繁等待
- CPU密集型 很少等待 优先级高
当IO频繁等待时,CPU开始调度提升IO优先级,提升优先级的因素:
1,用户指定优先级
2,进入等待的频繁程度
3,长时间不执行会提升优先级
atomic与nonatomic 的区别
- iOS 开发的建议
1,所有属性都声明为 nonatomic
2,尽量避免多线程抢夺同⼀块资源
3,尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减⼩移动客户端的压⼒ - nonatomic:⾮原⼦属性,⾮线程安全,适合内存⼩的移动设备
- atomic:原⼦属性(线程安全),针对多线程设计的,默认值,需要消耗⼤量的资源
单写多读:单个线程写⼊,多个线程可以读取
atomic 本身只是标识符,内部底层逻辑就有⼀把锁(⾃旋锁)
保证同⼀时间只有⼀个线程能够写⼊(但是同⼀个时间多个线程都可以取值)
GCD分析
GCD 简介
- 什么是GCD?
全称是 Grand Central Dispatch
纯 C 语⾔,提供了⾮常多强⼤的函数 - GCD的优势
1,GCD 是苹果公司为多核的并⾏运算提出的解决⽅案
2,GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)
3,GCD 会⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)
4,程序员只需要告诉 GCD 想要执⾏什么任务,不需要编写任何线程管理代码,将任务添加到队列,并且指定执⾏任务的函数
GCD探索
根据打印:
<OS_dispatch_queue_serial: cooci>-<OS_dispatch_queue_concurrent: cooci>-<OS_dispatch_queue_main: com.apple.main-thread>-<OS_dispatch_queue_global: com.apple.root.default-qos>
- 探索libdispatch源码:
主队列探索(com.apple.main-thread)
dispatch_queue_static_s
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
};
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) | DISPATCH_QUEUE_ROLE_BASE_ANON, .dq_label = "com.apple.main-thread", .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
表明dispatch_get_main_queue()主队列为串型队列。
串型队列特性
- 首先要知道串型和并发的区别在创建的时候
dispatch_queue_create
的区别,那么我们需要探索dispatch_queue_create
_dispatch_lane_create_with_target
进入到_dispatch_lane_create_with_target
方法,核心方法,创建和初始化:
_dispatch_queue_init
进入到_dispatch_queue_init
static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
uint16_t width, uint64_t initial_state_bits)
{
uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
dispatch_queue_t dq = dqu._dq;
dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
DISPATCH_QUEUE_INACTIVE)) == 0);
if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
dq->do_ref_cnt++; // released when DSF_DELETED is set
}
}
dq_state |= initial_state_bits;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width);
os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
dq->dq_state = dq_state;
dq->dq_serialnum =
os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
return dqu;
}
里面dqf |= DQF_WIDTH(width);
表明主队列为为串型队列。
dq_serialnum,_dispatch_queue_serial_numbers为标识number。
- 探索_dispatch_queue_serial_numbers:
其宏定义为17
// skip zero
// 1 - main_q
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
#define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
extern unsigned long volatile _dispatch_queue_serial_numbers;
代表创建的队列号number。
- 探索os_atomic_inc_orig:
#define os_atomic_inc_orig(p, m) \
os_atomic_add_orig((p), 1, m)
#define os_atomic_add_orig(p, v, m) \
_os_atomic_c11_op_orig((p), (v), m, add, +)
#define _os_atomic_c11_op_orig(p, v, m, o, op) \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
memory_order_##m)
那么为什么dispatch创建却是_dispatch_object_alloc,为什么不是_dispatch_queue_alloc呢?代码如下:
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s)); // alloc
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
_dispatch_object_alloc
- 探索
_dispatch_object_alloc
实现:
void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
const struct dispatch_object_vtable_s *_vtable = vtable;
dispatch_object_t dou;
dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);
dou._do->do_vtable = vtable;
return dou._do;
#else
return _os_object_alloc_realized(vtable, size);
#endif
}
_os_object_alloc_realized
_os_object_t
_os_object_alloc_realized(const void *cls, size_t size)
{
dispatch_assert(size >= sizeof(struct _os_object_s));
return _os_objc_alloc(cls, size);
}
_os_objc_alloc
static inline id
_os_objc_alloc(Class cls, size_t size)
{
id obj;
size -= sizeof(((struct _os_object_s *)NULL)->os_obj_isa);
while (unlikely(!(obj = class_createInstance(cls, size)))) {
_dispatch_temporary_resource_shortage();
}
return obj;
}
_dispatch_temporary_resource_shortage
void
_dispatch_temporary_resource_shortage(void)
{
sleep(1);
__asm__ __volatile__(""); // prevent tailcall
}
全局并发队列(com.apple.root.default-qos)
//其本质为结构体
struct dispatch_queue_global_s _dispatch_root_queues[] = {}
GCD底层源码继承链
dispatch_queue_t -> dispatch_queue_s -> dispatch_object_s -> dispatch_object_t
typedef struct dispatch_object_s {
private:
dispatch_object_s();
~dispatch_object_s();
dispatch_object_s(const dispatch_object_s &);
void operator=(const dispatch_object_s &);
} *dispatch_object_t;
#define DISPATCH_DECL(name) \
typedef struct name##_s : public dispatch_object_s {} *name##_t
#define DISPATCH_DECL_SUBCLASS(name, base) \
typedef struct name##_s : public base##_s {} *name##_t
#define DISPATCH_GLOBAL_OBJECT(type, object) (static_cast<type>(&(object)))
#define DISPATCH_RETURNS_RETAINED
#else /* Plain C */
#ifndef __DISPATCH_BUILDING_DISPATCH__
typedef union {
struct _os_object_s *_os_obj;
struct dispatch_object_s *_do;
struct dispatch_queue_s *_dq;
struct dispatch_queue_attr_s *_dqa;
struct dispatch_group_s *_dg;
struct dispatch_source_s *_ds;
struct dispatch_channel_s *_dch;
struct dispatch_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
struct dispatch_semaphore_s *_dsema;
struct dispatch_data_s *_ddata;
struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;
#endif // !__DISPATCH_BUILDING_DISPATCH__
dq_push
DISPATCH_VTABLE_INSTANCE(queue,
// This is the base class for queues, no objects of this type are made
.do_type = _DISPATCH_QUEUE_CLUSTER,
.do_dispose = _dispatch_object_no_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_object_no_invoke,
.dq_activate = _dispatch_queue_no_activate,
);
DISPATCH_VTABLE_INSTANCE(workloop,
.do_type = DISPATCH_WORKLOOP_TYPE,
.do_dispose = _dispatch_workloop_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_workloop_invoke,
.dq_activate = _dispatch_queue_no_activate,
.dq_wakeup = _dispatch_workloop_wakeup,
.dq_push = _dispatch_workloop_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_serial, lane,
.do_type = DISPATCH_QUEUE_SERIAL_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,
.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
.do_type = DISPATCH_QUEUE_CONCURRENT_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,
.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_concurrent_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
.do_type = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
.do_dispose = _dispatch_object_no_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_object_no_invoke,
.dq_activate = _dispatch_queue_no_activate,
.dq_wakeup = _dispatch_root_queue_wakeup,
.dq_push = _dispatch_root_queue_push,
);
- 进入全局队列:_dispatch_root_queue_push
void
_dispatch_root_queue_push(dispatch_queue_global_t rq, dispatch_object_t dou,
dispatch_qos_t qos)
{
#if DISPATCH_USE_KEVENT_WORKQUEUE
dispatch_deferred_items_t ddi = _dispatch_deferred_items_get();
if (unlikely(ddi && ddi->ddi_can_stash)) {
dispatch_object_t old_dou = ddi->ddi_stashed_dou;
dispatch_priority_t rq_overcommit;
rq_overcommit = rq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
if (likely(!old_dou._do || rq_overcommit)) {
dispatch_queue_global_t old_rq = ddi->ddi_stashed_rq;
dispatch_qos_t old_qos = ddi->ddi_stashed_qos;
ddi->ddi_stashed_rq = rq;
ddi->ddi_stashed_dou = dou;
ddi->ddi_stashed_qos = qos;
_dispatch_debug("deferring item %p, rq %p, qos %d",
dou._do, rq, qos);
if (rq_overcommit) {
ddi->ddi_can_stash = false;
}
if (likely(!old_dou._do)) {
return;
}
// push the previously stashed item
qos = old_qos;
rq = old_rq;
dou = old_dou;
}
}
#endif
#if HAVE_PTHREAD_WORKQUEUE_QOS
if (_dispatch_root_queue_push_needs_override(rq, qos)) {
return _dispatch_root_queue_push_override(rq, dou, qos);
}
#else
(void)qos;
#endif
_dispatch_root_queue_push_inline(rq, dou, dou, 1);
}
进入_dispatch_root_queue_push_inline
找其中_dispatch_root_queue_poke方法->_dispatch_root_queue_poke_slow
void
_dispatch_root_queue_poke(dispatch_queue_global_t dq, int n, int floor)
{
if (!_dispatch_queue_class_probe(dq)) {
return;
}
#if !DISPATCH_USE_INTERNAL_WORKQUEUE
#if DISPATCH_USE_PTHREAD_POOL
if (likely(dx_type(dq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE))
#endif
{
if (unlikely(!os_atomic_cmpxchg2o(dq, dgq_pending, 0, n, relaxed))) {
_dispatch_root_queue_debug("worker thread request still pending "
"for global queue: %p", dq);
return;
}
}
#endif // !DISPATCH_USE_INTERNAL_WORKQUEUE
return _dispatch_root_queue_poke_slow(dq, n, floor);
}
- 进入并发队列:
_dispatch_lane_concurrent_push
并找到_dispatch_lane_push
void
_dispatch_lane_concurrent_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
// <rdar://problem/24738102&24743140> reserving non barrier width
// doesn't fail if only the ENQUEUED bit is set (unlike its barrier
// width equivalent), so we have to check that this thread hasn't
// enqueued anything ahead of this call or we can break ordering
if (dq->dq_items_tail == NULL &&
!_dispatch_object_is_waiter(dou) &&
!_dispatch_object_is_barrier(dou) &&
_dispatch_queue_try_acquire_async(dq)) {
return _dispatch_continuation_redirect_push(dq, dou, qos);
}
_dispatch_lane_push(dq, dou, qos);
}
- 找到
_dispatch_lane_push
里面,其中 _dispatch_lane_push_waiter
为死锁dx_wakeup
是dq_push ->_dispatch_lane_wakeup
void
_dispatch_lane_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
dispatch_wakeup_flags_t flags = 0;
struct dispatch_object_s *prev;
if (unlikely(_dispatch_object_is_waiter(dou))) {
return _dispatch_lane_push_waiter(dq, dou._dsc, qos);
}
dispatch_assert(!_dispatch_object_is_global(dq));
qos = _dispatch_queue_push_qos(dq, qos);
// If we are going to call dx_wakeup(), the queue must be retained before
// the item we're pushing can be dequeued, which means:
// - before we exchange the tail if we have to override
// - before we set the head if we made the queue non empty.
// Otherwise, if preempted between one of these and the call to dx_wakeup()
// the blocks submitted to the queue may release the last reference to the
// queue when invoked by _dispatch_lane_drain. <rdar://problem/6932776>
prev = os_mpsc_push_update_tail(os_mpsc(dq, dq_items), dou._do, do_next);
if (unlikely(os_mpsc_push_was_empty(prev))) {
_dispatch_retain_2_unsafe(dq);
flags = DISPATCH_WAKEUP_CONSUME_2 | DISPATCH_WAKEUP_MAKE_DIRTY;
} else if (unlikely(_dispatch_queue_need_override(dq, qos))) {
// There's a race here, _dispatch_queue_need_override may read a stale
// dq_state value.
//
// If it's a stale load from the same drain streak, given that
// the max qos is monotonic, too old a read can only cause an
// unnecessary attempt at overriding which is harmless.
//
// We'll assume here that a stale load from an a previous drain streak
// never happens in practice.
_dispatch_retain_2_unsafe(dq);
flags = DISPATCH_WAKEUP_CONSUME_2;
}
os_mpsc_push_update_prev(os_mpsc(dq, dq_items), prev, dou._do, do_next);
if (flags) {
return dx_wakeup(dq, qos, flags);
}
}
void
_dispatch_lane_wakeup(dispatch_lane_class_t dqu, dispatch_qos_t qos,
dispatch_wakeup_flags_t flags)
{
dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;
if (unlikely(flags & DISPATCH_WAKEUP_BARRIER_COMPLETE)) {
return _dispatch_lane_barrier_complete(dqu, qos, flags);
}
if (_dispatch_queue_class_probe(dqu)) {
target = DISPATCH_QUEUE_WAKEUP_TARGET;
}
return _dispatch_queue_wakeup(dqu, qos, flags, target);
}
- 然后到
_dispatch_queue_wakeup
- 后到,_dispatch_lane_class_barrier_complete
异步并发堆栈流程:
dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
NSLog(@"12334");
});
frame #1: 0x0000000106c28848 libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #2: 0x0000000106c29a2c libdispatch.dylib`_dispatch_client_callout + 8
frame #3: 0x0000000106c2c339 libdispatch.dylib`_dispatch_continuation_pop + 594
frame #4: 0x0000000106c2b708 libdispatch.dylib`_dispatch_async_redirect_invoke + 778
frame #5: 0x0000000106c3ba09 libdispatch.dylib`_dispatch_root_queue_drain + 419
frame #6: 0x0000000106c3c4c3 libdispatch.dylib`_dispatch_worker_thread2 + 196
frame #7: 0x00007fff6da28417 libsystem_pthread.dylib`_pthread_wqthread + 244
frame #8: 0x00007fff6da2742f libsystem_pthread.dylib`start_wqthread + 15
单例底层原理
- 待后续有时间再补充
栅栏函数
- 最直接的作⽤: 控制任务执⾏顺序,同步
- dispatch_barrier_async 前⾯的任务执⾏完毕才会来到这⾥
- dispatch_barrier_sync 作⽤相同,但是这个会堵塞线程,影响后⾯的任务执⾏
- ⾮常重要的⼀点:
1, 栅栏函数只能控制同⼀自定义的并发队列
2,dispatch_get_global_queue(0, 0)全局并发队列中栅栏函数无效
- (void)demoBarrier{
// dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);//全局并发
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);//并发
/* 1.异步函数 */
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
/* 2. 栅栏函数 */
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"加载那么多,喘口气!!!");
});
NSLog(@"**********起来干!!");
}
栅栏函数底层分析:
- 通过对同步栅栏函数分析
dispatch_barrier_sync
:
dispatch_barrier_sync
->_dispatch_barrier_sync_f
->_dispatch_barrier_sync_f_inline
->_dispatch_sync_f_slow
->_dispatch_lane_non_barrier_complete
->_dispatch_lane_non_barrier_complete_finish
其中:DC_FLAG_BARRIER处理
主要方法:_dispatch_lane_class_barrier_complete
函数:
- 通过对异步栅栏函数执行打印bt分析:
frame #1: 0x000000010bd3f848 libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #2: 0x000000010bd40a2c libdispatch.dylib`_dispatch_client_callout + 8
frame #3: 0x000000010bd5188b libdispatch.dylib`_dispatch_lane_concurrent_drain + 1252
frame #4: 0x000000010bd48179 libdispatch.dylib`_dispatch_lane_invoke + 625
frame #5: 0x000000010bd42c71 libdispatch.dylib`_dispatch_queue_override_invoke + 584
frame #6: 0x000000010bd52a09 libdispatch.dylib`_dispatch_root_queue_drain + 419
frame #7: 0x000000010bd534c3 libdispatch.dylib`_dispatch_worker_thread2 + 196
frame #8: 0x00007fff6da28417 libsystem_pthread.dylib`_pthread_wqthread + 244
frame #9: 0x00007fff6da2742f libsystem_pthread.dylib`start_wqthread + 15
- 我们查看libdispatch底层源码,找到
dispatch_barrier_async
函数:
#ifdef __BLOCKS__
void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc_flags);
}
#endif
- 看到创建dc,并初始化qos,然后执行
_dispatch_continuation_async
函数:
- 然后到dx_push在上面我们探索到dx_push跟dq_push有关
调度组
通过上面栅栏函数发现它只能在同一个自定义的并发队列中使用,但实际场景中,我们可能同时开辟了多个并发队列,那栅栏函数就无效了,为了解决这种问题,调度组就诞生了:
- 最直接的作⽤: 控制任务执⾏顺序
- dispatch_group_create 创建组
- dispatch_group_async 进组任务, 里面包含dispatch_group_enter 以及dispatch_group_leave
- dispatch_group_notify 进组任务执⾏完毕通知
- dispatch_group_wait 进组任务执⾏等待时间
dispatch_group_enter 进组
dispatch_group_leave 出组
注意搭配使⽤
源码实现案例
- (void)groupDemo2{
//创建调度组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//调度进组任务 -里面包含:dispatch_group_enter 以及dispatch_group_leave
dispatch_group_async(group, queue, ^{
NSString *logoStr1 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
[self.mArray addObject:image1];
sleep(4);
});
// dispatch_async(queue, ^{
// NSString *logoStr1 = @"https://f12.baidu.com/it/u=3172787957,1000491180&fm=72";
// NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
// UIImage *image1 = [UIImage imageWithData:data1];
// [self.mArray addObject:image1];
dispatch_group_leave(group);
// });
dispatch_group_enter(group);
//进组任务执行完成回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
UIImage *newImage = nil;
NSLog(@"数组个数:%ld",self.mArray.count);
for (int i = 0; i<self.mArray.count; i++) {
UIImage *waterImage = self.mArray[i];
newImage =[KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
}
});
dispatch_group_leave(group);
}
信号量
信号量同步执行,可以当锁使用,可以控制并发最大数量。
- 信号量dispatch_semaphore_t
- dispatch_semaphore_create 创建信号量并控制每次通过个数,当两个线程需要协调时,值传递零非常有用,特定事件的完成。传递大于零的值是用于管理有限的资源池,其中池大小与值相等。
- dispatch_semaphore_wait 信号量等待
- dispatch_semaphore_signal 信号量释放
同步->当锁, 控制GCD最⼤并发数
底层源码探索
- dispatch_semaphore_wait在底层是个do…while循环,
如果是DISPATCH_TIME_NOW会超时处理,
DISPATCH_TIME_FOREVER会等待任务
do…while循环一直减,直到value 等于 0
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
- dispatch_semaphore_signal
-> _dispatch_semaphore_signal_slow
do…while循环一直加,直到value 等于 0
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
源码实现案例
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(3);
dispatch_queue_t queue1 = dispatch_queue_create("likeIos", NULL);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(sem);
});
//任务2
dispatch_async(queue1, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(sem);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(2);
NSLog(@"任务3完成");
dispatch_semaphore_signal(sem);
});
//任务4
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务4");
sleep(1);
NSLog(@"任务4完成");
dispatch_semaphore_signal(sem);
});
Dispatch_Source
- 本质:通过条件来控制block执行
- 其 CPU 负荷⾮常⼩,尽量不占⽤资源
- 联结的优势
- 在任⼀线程上调⽤它的的⼀个函数 dispatch_source_merge_data 后,会执⾏
Dispatch Source 事先定义好的句柄(可以把句柄简单理解为⼀个 block )
这个过程叫 Custom event ,⽤户事件。是 dispatch source ⽀持处理的⼀种事件 - 句柄是⼀种指向指针的指针 它指向的就是⼀个类或者结构,它和系统有很密切的关系
HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON
(图标句柄)等。这当中还有⼀个通⽤的句柄,就是HANDLE
- dispatch_source_create 创建源
- dispatch_source_set_event_handler 设置源事件回调
- dispatch_source_merge_data 源事件设置数据
- dispatch_source_get_data 获取源事件数据
- dispatch_resume 继续
- dispatch_suspend 挂起
锁
- 加锁是为了保护线程或者代码执行安全,防止执行中被别的线程插入,而引起
资源抢夺
锁的分类
⾃旋锁
⾃旋锁
:线程反复检查锁变量是否可⽤。由于线程在这⼀过程中保持执⾏,
因此是⼀种忙等待。⼀旦获取了⾃旋锁,线程会⼀直保持该锁,直⾄显式释
放⾃旋锁。 ⾃旋锁避免了进程上下⽂的调度开销,因此对于线程只会阻塞很
短时间的场合是有效的。
互斥锁
互斥锁
:是⼀种⽤于多线程编程中,防⽌两条线程同时对同⼀公共资源(⽐如全局变量)进⾏读写的机制。该⽬的通过将代码切⽚成⼀个⼀个的临界区⽽达成。按照相应顺序(同步)- 这⾥属于互斥锁的有:
• NSLock
• pthread_mutex
• @synchronized
条件锁
条件锁
:就是条件变量,当进程的某些资源要求不满⾜时就进⼊休眠,也就
是锁住了。当资源被分配到了,条件锁打开,进程继续运⾏
• NSCondition
• NSConditionLock
递归锁
递归锁
:就是同⼀个线程可以加锁N次⽽不会引发死锁
• NSRecursiveLock
• pthread_mutex(recursive)
信号量
信号量
(semaphore):是⼀种更⾼级的同步机制,互斥锁可以说是
semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,⽤来实
现更加复杂的同步,⽽不单单是线程间互斥。
• dispatch_semaphore
小节总结:
基本的锁就包括了三类 ⾃旋锁 互斥锁 读写锁
条件锁,递归锁,信号量都是上层的封装和实现
总体就两种锁:自旋锁和互斥锁,严格的话自旋锁也是互斥锁
TLS 线程相关解释
线程局部存储(Thread Local Storage,TLS): 是操作系统为线
程单独提供的私有空间,通常只有有限的容量。Linux系统下
通常通过pthread库中的
pthread_key_create()、
pthread_getspecific()、
pthread_setspecific()、
pthread_key_delete()
锁性能分析
通过对锁的性能测试(以100000数据为例),模拟器中数据(同样机型,模拟器数据会比真机数据大一些):
OSSpinLock: 0.661016 ms
dispatch_semaphore_t: 1.123071 ms
os_unfair_lock_lock: 1.129985 ms
pthread_mutex_t: 1.607060 ms
NSlock: 1.868963 ms
NSCondition: 2.519965 ms
PTHREAD_MUTEX_RECURSIVE: 2.979994 ms
NSRecursiveLock: 5.754113 ms
NSConditionLock: 13.507962 ms
@synchronized: 5.754948 ms
是因为,如果是iPhone而不是模拟器时线程数为8,
⚠️:如果源码可编译,可改一下StripeCount值为1或者0,会绽放不一样的烟火
:
锁的性能数据
@synchronized
- 首先@synchronized代码块,然后有枷锁的效果和递归可重用
- 注意⚠️:
// 锁的对象 不要为空
// self 生命周期
// 方便存储 释放
// 真机 和 模拟器
@synchronized (self) {
//对象不要为空
}
@synchronized结构
- 把锁放到main函数文件,通过clang或者xcrun到main.mm
- 核心在
objc_sync_enter(_sync_obj);
objc_sync_exit(_sync_obj);
- 对这两个方法下符号断点:
- libobjc.A.dylib`objc_sync_enter,看到是在objc里面的objc_sync_enter,那么我们运行objc源码:
- 这个时候如果@synchronized(参数),其中参数传nil时,走objc_sync_nil(); 什么都不会做,那么重点就在关于obj的判断:因此在enter的时候加锁:
data->mutex.lock();
对于objc_sync_exit:
- 没有data时error,有data时:
bool okay = data->mutex.tryUnlock();
解锁。
核心代码:SyncData* data = id2data(obj, RELEASE);
SyncData分析
而其中核心SyncData单向链表:
- 其中DisguisedPtr:关联对象
- threadCount:递归锁
id2data分析
- 其中有线程暂存(TLS)和缓存(cache)两种。
- 内部有个锁:
SyncList分析
- 横向为哈希值一致的对象
- 第一次进入:
- 存储到tls_set_direct,以及cache->list:
- 有值了之后
@synchronized总结
synchronized底层源码中有两个重要参数:threadCount
(可多线程,表示被多少线程锁过),lockCount
(具备可递归,表示同一个线程被锁多少次),多线程可递归
1,TLS保障threadCount多少条线程对这个锁对象加锁,
2,lock++进入多少次
- sync全局哈希表,采用拉链法,拉链syncData
- sDatalist array 里面存的synclist(绑定的objc)
- objc_sync_enter/exit 成对对称,封装底层递归锁
- 两种存储:tls 和 cache
- 第一次syncData时 头插法 创建链表结构 并标记threadCount = 1
- 然后判断是不是同一个对象进来,如果是,在TLS里面lock++, 如果找不到时,会重新创建sync 并threadCount++
- 否则lock回 threadCount –
NSlock/ NSReLock/NSRecursiveLock分析
- NSLock案例:
- (void)lg_testNSLock{
NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
sleep(2);
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
[lock lock];
testMethod(10);
[lock unlock];
});
}
}
- @synchronized解决lock的多线程性,NSRecursiveLock没有了lock递归
// @synchronized (self) {}
- (void)lg_testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[self.recursiveLock lock];
if (value > 0) {
// sleep(2);
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[self.recursiveLock unlock];
};
testMethod(10);
});
}
}
NSCondition分析
NSCondition 的对象实际上作为⼀个锁和⼀个线程检查器:锁主要
为了当检测条件时保护数据源,执⾏条件引发的任务;线程检查器
主要是根据条件决定是否继续运⾏线程,即线程是否被阻塞
1:[condition lock];//⼀般⽤于多线程同时访问、修改同⼀个数据源,保证在同⼀
时间内数据源只被访问、修改⼀次,其他线程的命令需要在lock 外等待,只到
unlock ,才可访问
2:[condition unlock];//与lock 同时使⽤
3:[condition wait];//让当前线程处于等待状态
4:[condition signal];//CPU发信号告诉线程不⽤在等待,可以继续执⾏
- 一个生产消费模型,生产安全加锁保护,消费安全加锁保护,当供不应求时需要信号的介入,让消费等待。
NSConditionLock分析
- 1.1 NSConditionLock 是锁,⼀旦⼀个线程获得锁,其他线程⼀定等待
- 1.2 [xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的
condition) 那它能执⾏此⾏以下代码,如果已经有其他线程获得锁(可能是条件锁,或者⽆条件
锁),则等待,直⾄其他线程解锁 - 1.3 [xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的
condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且
没有其他线程获得该锁,则进⼊代码区,同时设置它获得该锁,其他任何线程都将等待它代码的
完成,直⾄它解锁。 - 1.4 [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
- 1.5 return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得
锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函
数的⽬的在于可以实现两种状态下的处理 - 1.6 所谓的condition就是整数,内部通过整数⽐较条件
- 源码案例:
- (void)lg_testConditonLock{
// 信号量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// [self.myLock mylockWithCondition:2];
[conditionLock lockWhenCondition:1];
NSLog(@"线程 1");
// [self.myLock myUnlockWithCondition:0];
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
// [self.myLock mylockWithCondition:1];
NSLog(@"线程 2");
// [self.myLock myUnlockWithCondition:1];
// self.myLock.value = 1;
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
[conditionLock lock];
NSLog(@"线程 3");
[conditionLock unlock];
});
}
// 结果打印线程为:2,1,3
分析:
- 首先都是全局异步线程,第二个线程设置Condition:2,先走lockWhenCondition:2的线程,这个线程的unlockWithCondition:1,然后会接着走第一个线程,因为第一个线程WhenCondition为1。所以必是先2 后 1
- 第三个线程,可能在2,1之前,也可能在2,1之后,而3又sleep了1s,因此3很可能在2,1之后。
自旋锁
- 当发现其他线程执行时,当前线程,会询问和忙等,耗费性能比较高,实用场景:短小的任务处理
互斥锁
- 发现其他线程执行时,当前线程会休眠(就绪状态),等待唤醒执行
- 互斥锁⼩结
- 保证锁内的代码,同⼀时间,只有⼀条线程能够执⾏!
- 互斥锁的锁定范围,应该尽量⼩,锁定范围越⼤,效率越差!
- 互斥锁参数
- 能够加锁的任意 NSObject 对象
- 注意:锁对象⼀定要保证所有的线程都能够访问
- 如果代码中只有⼀个地⽅需要加锁,⼤多都使⽤ self,这样可以避免单独再创建⼀个锁对象
读写锁
介绍
- 读写锁实际是⼀种特殊的⾃旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进⾏读访问,写者则需要对共享资源进⾏写操作。
- 这种锁相对于⾃旋锁⽽⾔,能提⾼并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最⼤可能的读者数为实际的逻辑CPU数。
0, 写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时。
1,既有读者⼜有写者。在读写锁保持期间也是抢占失效的。
2,如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。
3,如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁。
4,⼀次只有⼀个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁.
正是因为这个特性: - 当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.
- 当读写锁在读加锁状态时, 所有试图以读模式对它进⾏加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进⾏加锁, 它必须直到所有的线程释放锁.
- 通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞
- 随后的读模式锁请求, 这样可以避免读模式锁⻓期占⽤, ⽽等待的写模式锁请求⻓期阻塞.
- 读写锁适合于对数据结构的读次数⽐写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁⼜叫共享-独占锁.
原理
核心点:多读单写
,读写互斥
,写入时不能堵塞任务执行(使用栅栏函数实现写入
, 并发队列实现多读
)
- 读写锁适合于对数据结构的读次数⽐写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味
着独占, 所以读写锁⼜叫共享-独占锁.
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
成功则返回0, 出错则返回错误编号.
同互斥量以上, 在释放读写锁占⽤的内存之前, 需要先通过pthread_rwlock_destroy对读写锁进⾏清理⼯作, 释
放由init分配的资源.
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
这3个函数分别实现获取读锁, 获取写锁和释放锁的操作. 获取锁的两个函数是阻塞操作, 同样, ⾮阻塞的函数为:
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
⾮阻塞的获取锁操作, 如果可以获取则返回0, 否则返回错误的EBUSY.
线程和Runloop的关系
- 1:runloop与线程是⼀⼀对应的,⼀个runloop对应⼀个核⼼的线程,为什么说是核⼼的,是因为runloop是可以嵌套的,但是核⼼的只能有⼀个,他们的关系保存在⼀个全局的字典⾥。
- 2:runloop是来管理线程的,当线程的runloop被开启后,线程会在执⾏完任务后进⼊休眠状态,有了任务就会被唤醒去执⾏任务。
- 3:runloop在第⼀次获取时被创建,在线程结束时被销毁。
- 4:对于主线程来说,runloop在程序⼀启动就默认创建好了。
- 5:对于⼦线程来说,runloop是懒加载的,只有当我们使⽤的时候才会创建,所以在⼦线程⽤定时器要注意:确保⼦线程的runloop被创建,不然定时器不会回调。
Runloop
认识
Runloop一直在控制着app程序的运行,使线程保活。一直do…while循环,但又跟循环不一样。
它的作用:
• 保持程序的持续运行
• 处理APP中的各种事件(触摸、定时器、performSelector)
• 节省cpu资源、提供程序的性能:该做事就做事,该休息就休息
runloop探索
底层封装的是CFRunLoopRun,为do…while循环。
_CFRunLoopGet0 - > _CFRunLoopCreat()
__CFRunLoop为结构体类型。
- 线程通过 CFMutableDictionaryRef绑定runloop
- 每个线程都有个runloop,而runloop可以有多个mode,mode里面可以有多个source,多个timer,多个observer
统称item
• block应用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
• 调用timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
• 响应source0: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
• 响应source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
• GCD主队列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
• observer源: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
runloop原理
Runloop的几种状态
- 六种状态:
RunLoopEntry
RunLoopBeforeTimers
RunLoopBeforeWaiting
RunLoopAfterWaiting
RunLoopExit
RunLoopAllActivities