《iOS面試題整理》- GCD、多線程相關(guān)面試題

基本概念

進(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ù)安全

  1. 互斥鎖 : 新線程訪問時, 發(fā)現(xiàn)其他線程正在執(zhí)行鎖定的代碼, 新線程會進(jìn)入休眠
NSLock
pthread_mutex
@synchronized
  1. 自旋鎖
    忙等的鎖, 新線程會用死循環(huán)的方式, 一直等待鎖定的代碼執(zhí)行完成, 數(shù)據(jù)量少的時候用

  2. 條件鎖
    不滿足就休眠, 資源分配到了, 條件鎖打開, 進(jìn)程繼續(xù)運行, 例如:NSConditionLock

  3. 讀寫鎖
    用于解決多線程對公共資源讀寫問題怀薛。 讀操作可以并發(fā)重入, 寫操作是互斥的

// 遞歸鎖
 NSRecursiveLock
 pthread_mutex(PTHREAD_MUTEX_RECURSIVE)
  1. 信號量

面試題

  1. 在不同的隊列中, 執(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)建多個線程

  1. 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 方法琉挖,喚醒所有等待中的信號量。

  1. 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/

  1. @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 方法時候, 不做解鎖處理

  1. 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í)行

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芭梯,隨后出現(xiàn)的幾起案子险耀,更是在濱河造成了極大的恐慌,老刑警劉巖玖喘,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甩牺,死亡現(xiàn)場離奇詭異,居然都是意外死亡累奈,警方通過查閱死者的電腦和手機(jī)贬派,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門急但,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搞乏,你說我怎么就攤上這事波桩。” “怎么了请敦?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵镐躲,是天一觀的道長。 經(jīng)常有香客問我冬三,道長匀油,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任勾笆,我火速辦了婚禮敌蚜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窝爪。我一直安慰自己弛车,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布蒲每。 她就那樣靜靜地躺著纷跛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邀杏。 梳的紋絲不亂的頭發(fā)上贫奠,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音望蜡,去河邊找鬼唤崭。 笑死,一個胖子當(dāng)著我的面吹牛脖律,可吹牛的內(nèi)容都是我干的谢肾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼小泉,長吁一口氣:“原來是場噩夢啊……” “哼芦疏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起微姊,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤酸茴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后兢交,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弊决,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了飘诗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片与倡。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昆稿,靈堂內(nèi)的尸體忽然破棺而出纺座,到底是詐尸還是另有隱情,我是刑警寧澤溉潭,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布净响,位于F島的核電站,受9級特大地震影響喳瓣,放射性物質(zhì)發(fā)生泄漏馋贤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一畏陕、第九天 我趴在偏房一處隱蔽的房頂上張望配乓。 院中可真熱鬧,春花似錦惠毁、人聲如沸犹芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腰埂。三九已至,卻和暖如春蜈膨,著一層夾襖步出監(jiān)牢的瞬間屿笼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工翁巍, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留刁卜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓曙咽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挑辆。 傳聞我的和親對象是個殘疾皇子例朱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容