iOS多線程總結(jié)

iOS中的常見多線程方案

image.png

GCD的常用函數(shù)

GCD中有2個(gè)用來(lái)執(zhí)行任務(wù)的函數(shù)

  • 用同步的方式執(zhí)行任務(wù)
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    queue:隊(duì)列
    block:任務(wù)

  • 用異步的方式執(zhí)行任務(wù)
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD源碼

GCD的隊(duì)列

GCD的隊(duì)列可以分為2大類型

  • ==并發(fā)==隊(duì)列(Concurrent Dispatch Queue)
    可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
    并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

  • ==串行==隊(duì)列(Serial Dispatch Queue)
    讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))

容易混淆的術(shù)語(yǔ)

有4個(gè)術(shù)語(yǔ)比較容易混淆:==同步、異步、并發(fā)、串行==

同步和異步主要影響:能不能開啟新的線程

  • 同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
  • 異步:在新的線程中(主線程除外)執(zhí)行任務(wù),具備開啟新線程的能力

并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式

  • 并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
  • 串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)

各種隊(duì)列的執(zhí)行效果

image.png

==使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會(huì)卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)==

// 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");
    
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
    NSLog(@"執(zhí)行任務(wù)2");
});
    
NSLog(@"執(zhí)行任務(wù)3");
    
// dispatch_sync立馬在當(dāng)前線程同步執(zhí)行任務(wù)
 // 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");
    
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
   NSLog(@"執(zhí)行任務(wù)2");
        
dispatch_sync(queue, ^{ // 1
     NSLog(@"執(zhí)行任務(wù)3");
   });
    
   NSLog(@"執(zhí)行任務(wù)4");
});
    
NSLog(@"執(zhí)行任務(wù)5");

隊(duì)列組的使用

思考:如何用gcd實(shí)現(xiàn)以下功能
異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
等任務(wù)1、任務(wù)2都執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3

// 創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    // 創(chuàng)建并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加異步任務(wù)
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)2-%@", [NSThread currentThread]);
        }
    });
    
    // 等前面的任務(wù)執(zhí)行完畢后,會(huì)自動(dòng)執(zhí)行這個(gè)任務(wù)
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
        }
    });

多線程的安全隱患

資源共享

1塊資源可能會(huì)被多個(gè)線程共享,也就是==多個(gè)線程可能會(huì)訪問(wèn)同一塊資源==
比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象、同一個(gè)變量、同一個(gè)文件,當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)==數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全==問(wèn)題

多線程安全隱患示例 – 存錢取錢

存錢取錢

多線程安全隱患示例 – 賣票

賣票
多線程安全隱患分析

多線程安全隱患的解決方案

  • 解決方案:使用線程同步技術(shù)(同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)
  • 常見的線程同步技術(shù)是:加鎖

GNUstep

GNUstep是GNU計(jì)劃的項(xiàng)目之一,它將Cocoa的OC庫(kù)重新開源實(shí)現(xiàn)了一遍

GNUstep源碼地址

雖然GNUstep不是蘋果官方源碼,但還是具有一定的參考價(jià)值

iOS中的線程同步方案

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispatch_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized

OSSpinLock

OSSpinLock叫做”自旋鎖”,等待鎖的線程會(huì)處于忙等(busy-wait)狀態(tài),一直占用著CPU資源
目前已經(jīng)不再安全,可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題
如果等待鎖的線程優(yōu)先級(jí)較高,它會(huì)一直占用著CPU資源,優(yōu)先級(jí)低的線程就無(wú)法釋放鎖
需要導(dǎo)入頭文件#import <libkern/OSAtomic.h>

// 初始化鎖
OSSpinLock lock = OS_SPINLOCK_INIT;
//嘗試加鎖(如果需要等待就不加鎖,直接返回false;如果不需要等待就加鎖,返回true)
OSSpinLockTry(&lock)
// 加鎖
OSSpinLockLock(&lock); 
// 解鎖
OSSpinLockUnlock(&lock);

os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,從iOS10開始才支持
從底層調(diào)用看,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等
需要導(dǎo)入頭文件#import <os/lock.h>

-** Low-level lock的特點(diǎn)等不到鎖就休眠

//初始化
os_unfair_lock moneyLock = OS_UNFAIR_LOCK_INIT;
//嘗試加鎖
os_unfair_lock_trylock(&moneyLock);
//加鎖
os_unfair_lock_lock(&moneyLock);   
//具體操作
//解鎖
os_unfair_lock_unlock(&moneyLock);

pthread_mutex

mutex叫做==”互斥鎖”==,等待鎖的線程會(huì)處于休眠狀態(tài)
需要導(dǎo)入頭文件#import <pthread.h>

pthread_mutex_t moneyMutex;
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化鎖
pthread_mutex_init(mutex, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
//嘗試加鎖
pthread_mutex_trylock(&moneyMutex);
//加鎖
pthread_mutex_lock(&moneyMutex);
//具體操作
//解鎖
pthread_mutex_unlock(&moneyMutex);
//銷毀鎖
pthread_mutex_destroy(&moneyMutex);

Mutex type attributes

#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE     2
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL
  • pthread_mutex – 遞歸鎖
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  • pthread_mutex – 條件
pthread_cond_t cond;
// 初始化條件
pthread_cond_init(&_cond, NULL);

if (條件) {
    // 等待條件(進(jìn)入休眠,放開mutex鎖;唄喚醒后,會(huì)再次對(duì)mutex加鎖)
    pthread_cond_wait(&_cond, &_mutex);
}
    
// 信號(hào)(激活一個(gè)等待條件的線程)
pthread_cond_signal(&_cond);
// 廣播(激活所有等待條件的線程)
//pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);

NSLock、NSRecursiveLock

NSLock是對(duì)mutex普通鎖的封裝
NSRecursiveLock也是對(duì)mutex遞歸鎖的封裝,API跟NSLock基本一致

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> {

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NSCondition

==NSCondition==是對(duì)==mutex==和==cond==的封裝

@interface NSCondition : NSObject <NSLocking> {

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NSConditionLock

NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝,可以設(shè)置具體的條件值

@interface NSConditionLock : NSObject <NSLocking> {

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

dispatch_semaphore

==semaphore==叫做”信號(hào)量”
信號(hào)量的初始值,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量
信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問(wèn)資源,保證線程同步

//初始化信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// 如果信號(hào)量的值 > 0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
// 如果信號(hào)量的值 <= 0,就會(huì)休眠等待,直到信號(hào)量的值變成>0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 讓信號(hào)量的值+1
dispatch_semaphore_signal(self.semaphore);

Semaphore宏定義

#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
    semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);   

使用方式

SemaphoreBegin;    
// 操作 
SemaphoreEnd;

dispatch_queue

直接使用GCD的串行隊(duì)列,也是可以實(shí)現(xiàn)線程同步的

dispatch_queue_t moneyQueue;
self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(moneyQueue, ^{
       //任務(wù)
    });

@synchronized

==@synchronized==是對(duì)==mutex==遞歸鎖的封裝
源碼查看:objc4中的objc-sync.mm文件
@synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作

static NSObject *lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lock = [[NSObject alloc] init];
    });
    
    @synchronized(lock) {
        [super __saleTicket];
    }

iOS線程同步方案性能比較

性能從高到低排序

  1. os_unfair_lock(iOS10以后)
  2. OSSpinLock(廢棄,存在問(wèn)題)
  3. dispatch_semaphore
  4. pthread_mutex
  5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
  6. NSLock
  7. NSCondition
  8. pthread_mutex(recursive)
  9. NSRecursiveLock
  10. NSConditionLock
  11. @synchronized

自旋鎖、互斥鎖比較

  1. 什么情況使用自旋鎖比較劃算?
  • 預(yù)計(jì)線程等待鎖的時(shí)間很短
  • 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生
  • CPU資源不緊張
  • 多核處理器
  1. 什么情況使用互斥鎖比較劃算?
  • 預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng)
  • 單核處理器
  • 臨界區(qū)有IO操作
  • 臨界區(qū)代碼復(fù)雜或者循環(huán)量大
  • 臨界區(qū)競(jìng)爭(zhēng)非常激烈

atomic

atomic用于保證屬性setter、getter的原子性操作,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖
可以參考源碼objc4的objc-accessors.mm
它并不能保證使用屬性的過(guò)程是線程安全的

iOS中的讀寫安全方案

  1. 思考如何實(shí)現(xiàn)以下場(chǎng)景
  • 同一時(shí)間,只能有1個(gè)線程進(jìn)行寫的操作
  • 同一時(shí)間,允許有多個(gè)線程進(jìn)行讀的操作
  • 同一時(shí)間,不允許既有寫的操作,又有讀的操作
  1. 上面的場(chǎng)景就是典型的“多讀單寫”,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,iOS中的實(shí)現(xiàn)方案有
  • pthread_rwlock:讀寫鎖
  • dispatch_barrier_async:異步柵欄調(diào)用

pthread_rwlock

等待鎖的線程會(huì)進(jìn)入休眠

@property (assign, nonatomic) pthread_rwlock_t lock;
- (void)rwlockTest {
    // 初始化鎖
    pthread_rwlock_init(&_lock, NULL);
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}

- (void)read {
    pthread_rwlock_rdlock(&_lock);
    //讀任務(wù)
    pthread_rwlock_unlock(&_lock);
}

- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    //寫任務(wù)
    pthread_rwlock_unlock(&_lock);
}

- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}

dispatch_barrier_async

  • 這個(gè)函數(shù)傳入的并發(fā)隊(duì)列必須是自己通過(guò)dispatch_queue_cretate創(chuàng)建的
  • 如果傳入的是一個(gè)串行或是一個(gè)全局的并發(fā)隊(duì)列,那這個(gè)函數(shù)便等同于dispatch_async函數(shù)的效果
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        
        dispatch_async(self.queue, ^{
            [self read];//讀操作
        });
        
        dispatch_barrier_async(self.queue, ^{
            [self write];//寫操作
        });
    }
  • 隊(duì)列的特點(diǎn):排隊(duì)、FIFO
    dispatch_sync:立刻在當(dāng)前線程執(zhí)行任務(wù),執(zhí)行完畢才能繼續(xù)往下執(zhí)行

si指令

s->單步 step一行oc代碼
i->instruction一行匯編代碼
next -> 區(qū)別是會(huì)跳過(guò)函數(shù)
c -> continue 過(guò)掉當(dāng)前斷點(diǎn)

通過(guò)匯編代碼的調(diào)試可以看出

  • jne j->jump
    自旋鎖(while循環(huán))
    自旋鎖屬于高級(jí)鎖:不睡覺

  • syscall -> 系統(tǒng)函數(shù),睡覺去了

強(qiáng)引用thread無(wú)法解決線程的后續(xù)任務(wù)無(wú)法調(diào)用的問(wèn)題,使用runloop是為了讓線程保持激活狀態(tài)

線程同步的本質(zhì)是:多條線程不同時(shí)占用同一份資源,也就是只要按順序訪問(wèn)就不會(huì)有問(wèn)題。

信號(hào)量:控制最大并發(fā)數(shù)

synchronize是一個(gè)遞歸鎖,是對(duì)pthreadmutex的封裝
如何證明是遞歸鎖(如果可以遞歸調(diào)用,說(shuō)明是遞歸鎖)

lock和unlock之間的代碼叫臨界區(qū)

atomic

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

讀寫安全:多讀單寫
讀不會(huì)破壞以前的數(shù)據(jù)。
結(jié)果之所以出現(xiàn)問(wèn)題,就是因?yàn)榘藢?改)操作

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS多線程實(shí)踐中,常用的就是子線程執(zhí)行耗時(shí)操作,然后回到主線程刷新UI。在iOS中每個(gè)進(jìn)程啟動(dòng)后都會(huì)建立一個(gè)主線...
    jackyshan閱讀 1,573評(píng)論 2 12
  • 目錄 簡(jiǎn)述 NSThread GCD操作與隊(duì)列異步操作并行隊(duì)列同步操作并行隊(duì)列同步操作串行隊(duì)列異步操作串行隊(duì)列隊(duì)列...
    魚王00閱讀 566評(píng)論 0 2
  • 進(jìn)程 什么是進(jìn)程 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序 每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)...
    45b645c5912e閱讀 511評(píng)論 0 5
  • iOS多線程編程 基本知識(shí) 1. 進(jìn)程(process) 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)...
    陵無(wú)山閱讀 6,345評(píng)論 1 14
  • 今天在git提交時(shí)遇到一個(gè)問(wèn)題,處理了一個(gè)下午,最終的方案還是develop分支回滾,在此記錄,以儆效尤。 整個(gè)流...
    三斤牛肉閱讀 201評(píng)論 0 0

友情鏈接更多精彩內(nèi)容