基本概念
進(jìn)程和線程的區(qū)別
- 進(jìn)程是指系統(tǒng)中正在運行的一個應(yīng)用程序, 每個進(jìn)程之間是相互獨立的
- 一個進(jìn)程中可以有多條線程, 進(jìn)程的所有任務(wù)都在線程中執(zhí)行的
進(jìn)程的狀態(tài)
- 新建
- 就緒 : 線程對象加入線程池中等待 CPU 調(diào)度
- 運行 : CPU負(fù)責(zé)調(diào)度線程中線程的執(zhí)行, 線程執(zhí)行完成前, 狀態(tài)可能在就緒和運行之間來回切換
- 阻塞 : 滿足某個預(yù)定條件, 使用休眠或鎖, 阻塞線程執(zhí)行
- 死亡 : 線程執(zhí)行完畢, 或者內(nèi)部中止執(zhí)行線程對象
線程安全
多個線程同時訪問一塊資源, 容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全
- 互斥鎖 : 新線程訪問時, 發(fā)現(xiàn)其他線程正在執(zhí)行鎖定的代碼, 新線程會進(jìn)入休眠
NSLock
pthread_mutex
@synchronized
自旋鎖
忙等的鎖, 新線程會用死循環(huán)的方式, 一直等待鎖定的代碼執(zhí)行完成, 數(shù)據(jù)量少的時候用條件鎖
不滿足就休眠, 資源分配到了, 條件鎖打開, 進(jìn)程繼續(xù)運行, 例如:NSConditionLock讀寫鎖
用于解決多線程對公共資源讀寫問題怀薛。 讀操作可以并發(fā)重入, 寫操作是互斥的
// 遞歸鎖
NSRecursiveLock
pthread_mutex(PTHREAD_MUTEX_RECURSIVE)
- 信號量
面試題
- 在不同的隊列中, 執(zhí)行100次dispatch_async 會創(chuàng)建多少個線程
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrent = dispatch_queue_create("councurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++) {
dispatch_async(serial, ^{
NSLog(@" 1 ----%@ ",[NSThread currentThread]);
});
dispatch_async(concurrent, ^{
NSLog(@" 2----%@ ",[NSThread currentThread]);
});
dispatch_async(global, ^{
NSLog(@" 3----%@ ",[NSThread currentThread]);
});
}
串行隊列只會創(chuàng)建一個線程, global 和 自定義 concurrent 隊列會創(chuàng)建多個線程
- dispatch_once 的底層實現(xiàn)
void dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func) {
struct _dispatch_once_waiter_s * volatile *vval = (struct _dispatch_once_waiter_s**)val;
struct _dispatch_once_waiter_s dow = { NULL, 0 };
struct _dispatch_once_waiter_s *tail, *tmp;
_dispatch_thread_semaphore_t sema;
if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
_dispatch_client_callout(ctxt, func);
tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
tail = &dow;
while (tail != tmp) {
while (!tmp->dow_next) {
_dispatch_hardware_pause();
}
sema = tmp->dow_sema;
tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
_dispatch_thread_semaphore_signal(sema);
}
} else {
dow.dow_sema = _dispatch_get_thread_semaphore();
for (;;) {
tmp = *vval;
if (tmp == DISPATCH_ONCE_DONE) {
break;
}
dispatch_atomic_store_barrier();
if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
dow.dow_next = tmp;
_dispatch_thread_semaphore_wait(dow.dow_sema);
}
}
_dispatch_put_thread_semaphore(dow.dow_sema);
}
}
第一次調(diào)用: 此時外部傳進(jìn)來的 onceToken 還是空指針,所以 vval 為 NULL阔涉,if 判斷成立捧存。首先執(zhí)行 block,然后讓將 vval 的值設(shè)為 DISPATCH_ONCE_DONE 表示任務(wù)已經(jīng)完成袄友,同時用 tmp 保存先前的 vval殿托。此時,dow 也為空剧蚣,因此 while 判斷不成立支竹,代碼執(zhí)行結(jié)束。
同一線程第二次調(diào)用: 由于 vval 已經(jīng)變成了 DISPATCH_ONCE_DONE鸠按,因此 if 判斷不成立礼搁,進(jìn)入 else 分支的 for 循環(huán)。由于 tmp 就是 DISPATCH_ONCE_DONE目尖,所以循環(huán)退出馒吴,沒有做任何事。
多個線程同時調(diào)用: 由于 if 判斷中是一個原子性操作瑟曲,所以必然只有一個線程能進(jìn)入 if 分支饮戳,其他的進(jìn)入 else 分支。由于其他線程在調(diào)用函數(shù)時洞拨,vval 還不是 DISPATCH_ONCE_DONE莹捡,所以進(jìn)入到 for 循環(huán)的后半部分。這里構(gòu)造了一個鏈表扣甲,鏈表的每個節(jié)點上都調(diào)用了信號量的 wait 方法并阻塞,而在 if 分支中齿椅,則會依次遍歷所有的節(jié)點并調(diào)用 signal 方法琉挖,喚醒所有等待中的信號量。
- dispatch_barrier_async 底層實現(xiàn)
void dispatch_barrier_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
dispatch_continuation_t dc;
dc = fastpath(_dispatch_continuation_alloc_cacheonly());
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
dc->dc_func = func;
dc->dc_ctxt = ctxt;
_dispatch_queue_push(dq, dc);
}
static _dispatch_thread_semaphore_t _dispatch_queue_drain(dispatch_queue_t dq) {
while (dq->dq_items_tail) {
/* ... */
if (!DISPATCH_OBJ_IS_VTABLE(dc) && (long)dc->do_vtable & DISPATCH_OBJ_BARRIER_BIT) {
if (dq->dq_running > 1) {
goto out;
}
} else {
_dispatch_continuation_redirect(dq, dc);
continue;
}
}
out:
/* 不完整的 drain涣脚,需要清理現(xiàn)場 */
return sema; // 返回空的信號量
}
void _dispatch_queue_invoke(dispatch_queue_t dq) {
_dispatch_thread_semaphore_t sema = _dispatch_queue_drain(dq);
if (sema) {
_dispatch_thread_semaphore_signal(sema);
} else if (tq) {
return _dispatch_queue_push(tq, dq);
}
}
do_vtable 設(shè)定了標(biāo)志位 DISPATCH_OBJ_BARRIER_BIT
, 從隊列中取出任務(wù)執(zhí)行的時候遇見這個標(biāo)志位立即停止, 會終止循環(huán), 返回一個空的信號量, 然后調(diào)用 _dispatch_queue_push
手動把這個任務(wù)添加進(jìn)去
參考資料: http://ios.jobbole.com/88638/
- @synchronized 底層實現(xiàn)
- 傳入的對象在 block 里面被釋放或者被置為 nil 會怎樣 ?
主要是調(diào)用了 objc_sync_enter 和 objc_sync_exit 方法
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
/**
* Begin synchronizing on 'obj'.
* Allocates recursive pthread_mutex associated with 'obj' if needed.
*
* @param obj The object to begin synchronizing on.
*
* @return OBJC_SYNC_SUCCESS once lock is acquired.
*/
OBJC_EXPORT int objc_sync_enter(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0);
/**
* End synchronizing on 'obj'.
*
* @param obj The objet to end synchronizing on.
*
* @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
*/
OBJC_EXPORT int objc_sync_exit(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0);
typedef struct SyncData {
id object;
recursive_mutex_t mutex;
struct SyncData* nextData;
int threadCount;
} SyncData;
typedef struct SyncList {
SyncData *data;
spinlock_t lock;
} SyncList;
// Use multiple parallel lists to decrease contention among unrelated objects.
#define COUNT 16
#define HASH(obj) ((((uintptr_t)(obj)) >> 5) & (COUNT - 1))
#define LOCK_FOR_OBJ(obj) sDataLists[HASH(obj)].lock
#define LIST_FOR_OBJ(obj) sDataLists[HASH(obj)].data
static SyncList sDataLists[COUNT];
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
result = recursive_mutex_lock(&data->mutex);
require_noerr_string(result, done, "mutex_lock failed");
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
done:
return result;
}
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
result = recursive_mutex_unlock(&data->mutex);
require_noerr_string(result, done, "mutex_unlock failed");
} else {
// @synchronized(nil) does nothing
}
done:
if ( result == RECURSIVE_MUTEX_NOT_LOCKED )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
return result;
}
定義哈希算法將傳入的對象映射到數(shù)組上的一個下標(biāo), 對象指針在內(nèi)存的地址轉(zhuǎn)變成無符號整形并有移5位, 再跟 COUNT 做 & 運算, 這樣結(jié)果不會超出數(shù)組大小
使用遞歸鎖 mutex 來做同步, @synchronized(nil) 不起任何作用
如果在block 里面?zhèn)魅肓薾il, 將會從代碼中移走線程安全, 調(diào)用objc_sync_exit 方法時候, 不做解鎖處理
- dispatch_async 的底層實現(xiàn)
隊列是用來提交 block 的對象, 按照先入先出的順序進(jìn)行處理, GCD 底層會維護(hù)一個線程池, 用來執(zhí)行這些 bock示辈。
void dispatch_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
dispatch_continuation_t dc;
if (dq->dq_width == 1) {
return dispatch_barrier_async_f(dq, ctxt, func);
}
dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;
dc->dc_func = func;
dc->dc_ctxt = ctxt;
if (dq->do_targetq) {
return _dispatch_async_f2(dq, dc);
}
_dispatch_queue_push(dq, dc);
}
如果是串行隊列 dq_with == 1, 調(diào)用 dispatch_barrier_async_f 函數(shù)處理, 如果有 do_targetq 則進(jìn)行轉(zhuǎn)發(fā), 否則調(diào)用 _dispatch_queue_push 入隊
用鏈表保存所有提交的 block遣蚀,然后在底層線程池中矾麻,依次取出 block 并執(zhí)行