1. 程式人生 > >iOS底層原理之多執行緒

iOS底層原理之多執行緒

簡介

進階

GCD

  • 0GCD的常用函式:
    GCD中有2個用來執行任務的函式
  1. 用同步的方式執行任務
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  2. 用非同步的方式執行任務
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    queue:佇列
    block:任務
  1. 併發佇列(Concurrent Dispatch Queue)
    可以讓多個任務併發(同時)執行(自動開啟多個執行緒同時執行任務)
    併發功能只有在非同步(dispatch_async)函式下才有效
  2. 序列佇列(Serial Dispatch Queue)
    讓任務一個接著一個地執行(一個任務執行完畢後,再執行下一個任務)
  • 容易混淆的術語
  1. 同步和非同步主要影響:能不能開啟新的執行緒
    同步:在當前執行緒中執行任務,不具備開啟新執行緒的能力
    非同步:在新的執行緒中執行任務,具備開啟新執行緒的能力
  2. 併發和序列主要影響:任務的執行方式
    併發:多個任務併發(同時)執行
    序列:一個任務執行完畢後,再執行下一個任務
  • 發生死鎖的前提條件:使用sync函式當前序列佇列中新增任務,會卡住當前的序列佇列(產生死鎖)。

  • 特殊例項:

    1. 下面程式碼列印順序
     int main(int argc, const char * argv[]) {
      @autoreleasepool {
          NSLog(@"1---%@",[NSThread currentThread]);
          dispatch_sync(dispatch_get_global_queue(0, 0), ^{
             NSLog(@"2---%@",[NSThread currentThread]);
              dispatch_queue_t queue =  dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
              dispatch_async(queue, ^{
                 NSLog(@"3---%@",[NSThread currentThread]);
              });
          });
          NSLog(@"4---%@",[NSThread currentThread]);
      }
      return 0;
     }
    

    結果為1,2,4,3。並且在3的時候開啟了子執行緒。非同步開啟了子執行緒,序列執行是指如果在queue這個序列佇列中新增多個任務,裡面的任務會序列執行,與外面無關,所以並不是1,2,3,4。

    1. 下面列印結果,會不會產生死鎖:
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
         dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
         dispatch_async(queue, ^{
             NSLog(@"1");
             dispatch_sync(queue, ^{
                 NSLog(@"2");
             });
         });
         NSLog(@"3");
         
     }
     return 0;
    }
    

    列印結果為3,1,會產生死鎖,所以2列印不了。

    1. 下面程式碼列印結果
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        // 這句程式碼的本質是往Runloop中新增定時器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        //        [self performSelector:@selector(test) withObject:nil]; //本週是messageSend
        NSLog(@"3");
        //  [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}
- (void)test
{
    NSLog(@"2");
}

列印結果為1,3,因為performSelector:withObject:afterDelay:的本質是往Runloop中新增定時器
子執行緒預設沒有啟動Runloop,所以無法執行test方法,將runloop啟動即可。

  1. 下面程式碼列印結果
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 NSThread *thread = [[NSThread alloc] initWithBlock:^{
     NSLog(@"1");
     
//        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
 }];
 [thread start];
 [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
- (void)test
{
 NSLog(@"2");
}

列印結構為1,因為執行緒啟動後會執行以下block中的方法,執行完後就銷燬了,因為沒有啟動runloop。

多執行緒的安全隱患

  • 資源共享,一塊資源可能會被多個執行緒共享,也就是多個執行緒可能會訪問同一塊資源(比如多個執行緒訪問同一個物件、同一個變數、同一個檔案),當多個執行緒訪問同一塊資源時,很容易引發資料錯亂和資料安全問題。
    多執行緒安全隱患分析
    分析:公共資源初始值為17,Thread A,Thread B這時候都給公共資源加1,但是都拿到的值是17,加1返回的結果都是18,而正確的值應該是19才對。

多執行緒安全隱患的解決方案

  • 解決方案:使用執行緒同步技術(同步,就是協同步調,按預定的先後次序進行),常見的執行緒同步技術是:加鎖
    執行緒加上
    分析:當Thread A要訪問操作公共資源時,先給加鎖,鎖住這一塊公共資源,這時候別的執行緒就必須等待;等Thread A操作完成後,將這一塊資源解鎖,這時候Thread B才能進來操作公共資源,B也要給公共資源加鎖,操作完成後解鎖,以此類推。
    主要:給公共資源加的鎖必須是同一把鎖。
  • GNUstep
    GNUstep是GNU計劃的專案之一,它將Cocoa的OC庫重新開源實現了一遍,原始碼地址:http://www.gnustep.org/resources/downloads.php , 雖然GNUstep不是蘋果官方原始碼,但還是具有一定的參考價值。

iOS中的執行緒同步方案

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

1.OSSpinLock
  • OSSpinLock叫做”自旋鎖”,等待鎖的執行緒會處於忙等(busy-wait)狀態,一直佔用著CPU資源
    目前已經不再安全,可能會出現優先順序反轉問題
    如果等待鎖的執行緒優先順序較高,它會一直佔用著CPU資源,優先順序低的執行緒就無法釋放鎖
    需要匯入標頭檔案#import <libkern/OSAtomic.h>,起使用步驟如下
//初始化鎖
OSSpinLock lock = OS_SPINLOCK_INIT;
//嘗試枷鎖(如果需要等待就不要加鎖,直接返回返回false,如果不需要等到就枷鎖,返回true)
bool result = OSSpinLockTry(&lock);
// 加鎖
OSSpinLockLock(&lock);
// 解鎖
 OSSpinLockUnlock(&lock);

示例1:賣票

#import "ViewController.h"
#import <libkern/OSAtomic.h>
@interface ViewController ()
@property (assign, nonatomic) int ticketsCount;
@property (assign, nonatomic) OSSpinLock lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化鎖
    self.lock = OS_SPINLOCK_INIT;    
    [self ticketTest];
}

/*
 非同步併發賣票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });

/**
 賣1張票
 */
- (void)saleTicket
{
//    if (OSSpinLockTry(&_lock)) {
//        int oldTicketsCount = self.ticketsCount;
//        sleep(.2);
//        oldTicketsCount--;
//        self.ticketsCount = oldTicketsCount;
//        NSLog(@"還剩%d張票 - %@", oldTicketsCount, [NSThread currentThread]);
//
//        OSSpinLockUnlock(&_lock);
//    }
    
    // 加鎖
    OSSpinLockLock(&_lock);

    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"還剩%d張票 - %@", oldTicketsCount, [NSThread currentThread]);

    // 解鎖
    OSSpinLockUnlock(&_lock);
}

示例2.存錢取錢

//前面程式碼一樣省略
/**
 存錢、取錢演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self saveMoney];//存錢
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self drawMoney];//取錢
        }
    });
}

/**
 存錢
 */
- (void)saveMoney
{
    // 加鎖
    OSSpinLockLock(&_lock1);
    
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    NSLog(@"存50,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
    // 解鎖
    OSSpinLockUnlock(&_lock1);   
}

/**
 取錢
 */
- (void)drawMoney
{
    // 加鎖
    OSSpinLockLock(&_lock1);
    
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
    // 解鎖
    OSSpinLockUnlock(&_lock1);
}

注意:不管是買票還存錢取錢,只要同時訪問的是同一塊資源就要,加要加同一把鎖,也就說鎖變數是一個全域性變數或靜態變數,只初始化一次。

2. os_unfair_lock
  • os_unfair_lock用於取代不安全的OSSpinLock ,從iOS10開始才支援,從底層呼叫看,等待os_unfair_lock鎖的執行緒會處於休眠狀態,並非忙等,需要匯入標頭檔案#import <os/lock.h>,使用類似於OSSpinLock,這裡不再舉例。
        //初始化
        os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
        //嘗試加鎖
        os_unfair_lock_trylock(&lock);
        //加鎖
        os_unfair_lock_lock(&lock);
        //解鎖
        os_unfair_lock_unlock(&lock);
3. pthread_mutex
  • mutex叫做”互斥鎖”,等待鎖的執行緒會處於休眠狀態,需要匯入標頭檔案#import <pthread.h>
    // 初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);//預設模式
    // 初始化鎖
    pthread_mutex_t mutex1;
    pthread_mutex_init(mutex, &attr);
    //嘗試加鎖
    pthread_mutex_trylock(mutex);
    //加鎖
    pthread_mutex_lock(mutex);
    //解鎖
    pthread_mutex_unlock(mutex);
    // 銷燬屬性
    pthread_mutexattr_destroy(&attr);
    // 銷燬鎖
    pthread_mutex_destroy(mutex);
 
   //屬性相關模式
  	#define PTHREAD_MUTEX_NORMAL		0
	#define PTHREAD_MUTEX_ERRORCHECK	1
	#define PTHREAD_MUTEX_RECURSIVE		2 //遞迴模式
	#define PTHREAD_MUTEX_DEFAULT		PTHREAD_MUTEX_NORMAL //預設模式
  • 示例:
#import "MutexDemo.h"
#import <pthread.h>

@interface MutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
@end

@implementation MutexDemo

- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_ticketMutex];
        [self __initMutex:&_moneyMutex];
    }
    return self;
}
- (void)__initMutex:(pthread_mutex_t *)mutex
{
    // 靜態初始化
    // pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    // 初始化屬性
    // pthread_mutexattr_t attr;
    // pthread_mutexattr_init(&attr);
    // pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);//預設模式
    // 初始化鎖
    // pthread_mutex_init(mutex, &attr);
    pthread_mutex_init(mutex, NULL);//傳NULL就是預設模式
}

//賣票
- (void)__saleTicket
{
   //加鎖
    pthread_mutex_lock(&_ticketMutex);
    [super __saleTicket];
    //解鎖
    pthread_mutex_unlock(&_ticketMutex);
}
//存錢
- (void)__saveMoney
{
    pthread_mutex_lock(&_moneyMutex);
    [super __saveMoney];
    pthread_mutex_unlock(&_moneyMutex);
}
//取錢
- (void)__drawMoney
{
    pthread_mutex_lock(&_moneyMutex);
    [super __drawMoney];
    pthread_mutex_unlock(&_moneyMutex);
}
//銷燬屬性和鎖
- (void)dealloc
{
    pthread_mutex_destroy(&_moneyMutex);
    pthread_mutex_destroy(&_ticketMutex);
}
@end
pthread_mutex – 遞迴鎖

有時候我們可能會對一塊資源遞迴訪問操作,如果用上面預設型別的互斥鎖就會產生死鎖的情況,這時候用遞迴鎖就可以解決,只需要把屬性型別設定為PTHREAD_MUTEX_RECURSIVE

  • 示例:死鎖
#import "MutexDemo2.h"
#import <pthread.h>
@interface MutexDemo2()
@property (assign, nonatomic) pthread_mutex_t mutex;
@end
@implementation MutexDemo2
- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_mutex];
    }
    return self;
}
- (void)__initMutex:(pthread_mutex_t *)mutex
{
    // 遞迴鎖:允許同一個執行緒對一把鎖進行重複加鎖
    
    // 初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    // 初始化鎖
    pthread_mutex_init(mutex, &attr);
    // 銷燬屬性
    pthread_mutexattr_destroy(&attr);
}
- (void)otherTest
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"%s", __func__);
    static int count = 0;
    if (count < 10) {
        count++;
        [self otherTest];
    }
    pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
}
@end

分析:上面otherTest方法第一次進來會加鎖,然後遞迴調自己,這時候第二次進來又要加鎖,但這時候第一次加的鎖還沒有開啟就加不了鎖,也就說第一次的鎖沒有開啟,第二次又要加鎖是加不上的,第二次執行緒就一直處在水面狀態,就一直卡在那。
解決方案:只需將屬性型別設定為遞迴鎖,pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

pthread_mutex – 條件
  • 有時候我們需要在A執行緒做完一部分事情後去B執行緒做事情,等B做完事情後又回到A做事情(生產者-消費者模式),這時候就需要用到pthread_mutex – 條件,也稱為條件鎖。
//初始化鎖
    pthread_mutex_t mutex;
    pthread_mutex_init(&_mutex, NULL);
    //初始化條件
    pthread_cond_t condition;
    pthread_cond_init(&condition, NULL);
    //等待條件(執行緒進入休眠,放開mutex鎖,被喚醒後會再次mutex加鎖)
    pthread_cond_wait(&condition, &mutex);
    //啟用一個等待條件的執行緒(發訊號)
    pthread_cond_signal(&condition);
    //啟用所有等待條件的執行緒(發廣播)
    pthread_cond_broadcast(&condition);
    //銷燬執行緒
    pthread_mutex_destroy(&mutex);
    //銷燬條件
    pthread_cond_destroy(&condition);
  • 示例:
#import "MutexDemo3.h"
#import <pthread.h>
@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation MutexDemo3
- (instancetype)init
{
    if (self = [super init]) {
        // 初始化屬性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化鎖
        pthread_mutex_init(&_mutex, &attr);
        // 銷燬屬性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化條件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}
- (void)otherTest
{
    //開啟兩個子執行緒執行刪除和新增元素操作
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 生產者-消費者模式
// 執行緒1
// 刪除陣列中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待,這時候就會解鎖,執行緒2就可以加鎖執行其新增操作
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    pthread_mutex_unlock(&_mutex);
}
// 執行緒2
// 往陣列中新增元素
- (void)__add
{   NSLog(@"__add - begin");
    pthread_mutex_lock(&_mutex);
    sleep(5);
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 發訊號啟用執行緒1的等待條件
    pthread_cond_signal(&_cond);
    // 廣播
//    pthread_cond_broadcast(&_cond);
    //當執行緒2的鎖解開後,執行緒1就會加鎖,執行等待條件後面的程式碼
    pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}
@end

結果及分析:不管哪個子執行緒先執行,“新增元素”總是先於“刪除元素自行”。
注意pthread_cond_signal(&_cond);當這句程式碼執行發射訊號啟用子執行緒1的條件時,並不是立馬就去線上程1中去加鎖,而是等到執行緒2中的鎖解開後採取執行緒1中加鎖,也就是說訊號發出後,執行緒1一直在等待執行緒2的解鎖。

4. NSLock、NSRecursiveLock、NSCondition、NSConditionLock
NSLock
  • NSLock是對mutex普通鎖的封裝;
//嘗試加鎖
- (BOOL)tryLock;
//嘗試在某個日期之前加鎖
- (BOOL)lockBeforeDate:(NSDate *)limit;
//加鎖解鎖的操作在 NSLocking協議裡面
@protocol NSLocking
//加鎖
- (void)lock;
//解鎖
- (void)unlock;
@end

示例:

#import "NSLockDemo.h"

@interface NSLockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@property (strong, nonatomic) NSLock *moneyLock;
@end

@implementation NSLockDemo
- (instancetype)init
{
    if (self = [super init]) {
        //初始化鎖
        self.ticketLock = [[NSLock alloc] init];
        self.moneyLock = [[NSLock alloc] init];
    }
    return self;
}
//賣票
- (void)__saleTicket
{
    [self.ticketLock lock];
    
    [super __saleTicket];
    
    [self.ticketLock unlock];
}
//存錢
- (void)__saveMoney
{
    [self.moneyLock lock];
    [super __saveMoney];
    [self.moneyLock unlock];
}
//取錢
- (void)__drawMoney
{
    [self.moneyLock lock];
    [super __drawMoney];
    [self.moneyLock unlock];
}
@end
NSRecursiveLock
  • NSRecursiveLock也是對mutex遞迴鎖的封裝,API跟NSLock基本一致;
  • NSRecursiveLock示例:
#import "NSRecursiveLockDemo.h"
@interface NSRecursiveLockDemo()
@property (strong, nonatomic) NSRecursiveLock *lock;
@end
@implementation NSRecursiveLockDemo
- (instancetype)init
{
    if (self = [super init]) {
        //初始化鎖
        self.lock = [[NSRecursiveLock alloc]init];
    }
    return self;
}
//遞迴鎖
-(void)otherTest{
    [self.lock lock];
    static int count = 0;
    if (count < 10) {
        NSLog(@"%d",count);
        count++;
        [self otherTest];
    }
    [self.lock unlock];
}
@end
NSCondition
  • NSCondition是對mutexcond的封裝。
//條件等待
- (void)wait;
//嘗試在某個日子加鎖
- (BOOL)waitUntilDate:(NSDate *)limit;
//發射訊號啟用條件
- (void)signal;
//發射廣播啟用條件
- (void)broadcast;
  • 示例:
#import "NSConditionDemo.h"
@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation NSConditionDemo
- (instancetype)init
{
    if (self = [super init]) {
        //初始化鎖和條件
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}
- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 生產者-消費者模式
// 執行緒1
// 刪除陣列中的元素
- (void)__remove
{
    [self.condition lock];
    NSLog(@"__remove - begin");
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    [self.condition unlock];
}
// 執行緒2
// 往陣列中新增元素
- (void)__add
{
    [self.condition lock];
    sleep(1);
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 發射訊號啟用條件
    [self.condition signal];
    // 廣播
    //[self.condition broadcast];
    [self.condition unlock];
}
@end
NSConditionLock
  • NSConditionLock是對NSCondition的進一步封裝,可以設定具體的條件值。
//初始化
- (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;
  • 示例:
#import "NSConditionLockDemo.h"
@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
- (instancetype)init
{
    if (self = [super init]) {
        //初始化鎖和條件,並設定條件為1
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
        //預設條件為0
//        self.conditionLock = [[NSConditionLock alloc]init];
    }
    return self;
}

- (void)otherTest
{   //建立三個執行緒執行三個任務
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}
- (void)__one
{
    [self.conditionLock lock];
    //在條件為1時加鎖
//    [self.conditionLock lockWhenCondition:1];
    NSLog(@"__one");
    sleep(1);
    //解開當前所,並設定條件為為2
    [self.conditionLock unlockWithCondition:2];
}
- (void)__two
{   //在條件為2時加鎖
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"__two");
    sleep(1);
    //解開當前所,並設定條件為3
    [self.conditionLock unlockWithCondition:3];
}
- (void)__three
{   //在條件為3時加鎖
    [self.conditionLock lockWhenCondition:3];
    NSLog(@"__three");
    //解鎖
    [self.conditionLock unlock];
}
@end

結果及分析:不管開啟的三條子執行緒執行任務的順序是什麼都會按__one__twothree的順序列印,因為當三天執行緒開啟執行任務時,因為初始化的條件為1,但是沒有找到就會去執行lock方法來加鎖,然後依次執行到unlockWithCondition:解鎖並重新設定條件,就會給對應條件下的程式碼加鎖並執行裡面的程式碼。(也就是說會個加個條件標籤,通過判斷標籤為幾就去加鎖並執行裡面的程式碼,如果不是對應的標籤都需要睡眠等待)。

5.dispatch_semaphore
  • semaphore叫做”訊號量”,訊號量的初始值,可以用來控制執行緒併發訪問的最大數量,訊號量的初始值為1,代表同時只允許1條執行緒訪問資源,保證執行緒同步。
//初始化訊號量
dispatch_semaphore_create(long value);
 // 如果訊號量的值 > 0,就讓訊號量的值減1,然後繼續往下執行程式碼
// 如果訊號量的值 <= 0,就會休眠等待,直到訊號量的值變成>0,就讓訊號量的值減1,然後繼續往下執行程式碼
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
// 讓訊號量的值+1
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
  • 示例:
#import "SemaphoreDemo.h"
@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end
@implementation SemaphoreDemo
- (instancetype)init
{
    if (self = [super init]) {
        //初始化,最大併發值為5
        self.semaphore = dispatch_semaphore_create(5);
        self.ticketSemaphore = dispatch_semaphore_create(1);
        self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
}
//取錢
- (void)__drawMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    [super __drawMoney];
    dispatch_semaphore_signal(self.moneySemaphore);
}
//存錢
- (void)__saveMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    [super __saveMoney];
    dispatch_semaphore_signal(self.moneySemaphore);
}
//賣票
- (void)__saleTicket
{
    dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
    [super __saleTicket];
    dispatch_semaphore_signal(self.ticketSemaphore);
}
- (void)otherTest
{   //開啟20條執行緒
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}
//訊號量設定為5,所以最多是5條執行緒同時執行任務
- (void)test
{
    // 如果訊號量的值 > 0,就讓訊號量的值減1,然後繼續往下執行程式碼
    // 如果訊號量的值 <= 0,就會休眠等待,直到訊號量的值變成>0,就讓訊號量的值減1,然後繼續往下執行程式碼
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    // 讓訊號量的值+1
    dispatch_semaphore_signal(self.semaphore);
}
@end
6.dispatch_queue
  • 直接使用GCD的序列佇列,也是可以實現執行緒同步的。
//建立穿行佇列
dispatch_queue_t queue = dispatch_queue_create("lockQueue", DISPATCH_QUEUE_SERIAL);
//執行佇列中的任務
dispatch_sync(queue, ^{
 //執行任務
    });

示例:

#import "SerialQueueDemo.h"
@interface SerialQueueDemo()
@property (strong, nonatomic) dispatch_queue_t ticketQueue;
@property (strong, nonatomic) dispatch_queue_t moneyQueue;
@end
@implementation SerialQueueDemo
- (instancetype)init
{
    if (self = [super init]) {
        self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
        self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
//取錢
- (void)__drawMoney
{
    dispatch_sync(self.moneyQueue, ^{
        [super __drawMoney];
    });
}
//存錢
- (void)__saveMoney
{
    dispatch_sync(self.moneyQueue, ^{
        [super __saveMoney];
    });
}
//賣票
- (void)__saleTicket
{
    dispatch_sync(self.ticketQueue, ^{
        [super __saleTicket];
    });
}
@end

注意,這裡也可以用非同步執行函式dispatch_async,只要保證要訪問的同一塊資源的任務放在同一個序列佇列中就可以按順序同步執行了。

7.NSOperationQueue
  • 可以通過設定NSOperationQueue的最大併發數來實現執行緒同步。
        //初始化
        NSOperationQueue * queue = [[NSOperationQueue alloc]init];
        //設定最大併發數
        queue.maxConcurrentOperationCount = 1;
        //新增操作並執行佇列中的任務
        [queue addOperationWithBlock:^{
        //任務
    }];
  • 示例:
#import "NSOperationQueueDemo.h"
@interface NSOperationQueueDemo()
@property(strong, nonatomic)NSOperationQueue * moneyQueue;
@property(strong, nonatomic)NSOperationQueue * ticketQueue;
@end
@implementation NSOperationQueueDemo
-(instancetype)init{
    if (self = [super init]) {
        self.moneyQueue = [[NSOperationQueue alloc]init];
        self.moneyQueue.maxConcurrentOperationCount = 1;
        self.ticketQueue = [[NSOperationQueue alloc]init];
        self.ticketQueue.maxConcurrentOperationCount = 1;
    }
    return self;
}
- (void)__saveMoney{
    
    [self.moneyQueue addOperationWithBlock:^{
        [super __saveMoney];
    }];
    
}
- (void)__drawMoney{
    [self.moneyQueue addOperationWithBlock:^{
        [super __drawMoney];
    }];
}
- (void)__saleTicket{
    [self.ticketQueue addOperationWithBlock:^{
        [super __saleTicket];
    }];
}
@end

開啟了執行緒,但是認為是穿行執行的,這樣就保證的執行緒同步。
注意:訪問同一塊資源的的任務要要新增到同一個佇列中。

[email protected]
  • @synchronized是對mutex遞迴鎖的封裝,原始碼檢視:objc4中的objc-sync.mm檔案,@synchronized(obj)內部會生成obj對應的遞迴鎖,然後進行加鎖、解鎖操作。
@synchronized(obj) {
//任務
    }
  • 示例:
#import "SynchronizedDemo.h"
@implementation SynchronizedDemo
//取錢
- (void)__drawMoney
{
    @synchronized([self class]) {
        [super __drawMoney];
    }
}
//存錢
- (void)__saveMoney
{
    @synchronized([self class]) { // objc_sync_enter
        [super __saveMoney];
    } // objc_sync_exit
}
//賣票
- (void)__saleTicket
{
    static NSObject *lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lock = [[NSObject alloc] init];
    });
    
    @synchronized(lock) {
        [super __saleTicket];
    }
}
//遞迴鎖
- (void)otherTest
{
    @synchronized([self class]) {
        static int a = 0;
        if (a < 10) {
            a++;
        NSLog(@"%d",a);
        [self otherTest];
        }
    }
}
@end

注意:傳進取的obc可以是任意的例項物件和類物件,訪問統一塊資源必須使用通一把鎖(竄進去的必須是同一個物件)。

iOS執行緒同步方案效能比較

效能從高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSOperationQueue(queue.maxConcurrentOperationCount = 1)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized

自旋鎖、互斥鎖比較

什麼情況使用自旋鎖比較划算?
預計執行緒等待鎖的時間很短;
加鎖的程式碼(臨界區)經常被呼叫,但競爭情況很少發生;
CPU資源不緊張;
多核處理器。

什麼情況使用互斥鎖比較划算?
預計執行緒等待鎖的時間較長;
臨界區有IO(讀寫)操作;
臨界區程式碼複雜或者迴圈量大;
臨界區競爭非常激烈;
單核處理器。

atomic

  • atomic用於保證屬性settergetter的原子性操作,相當於在gettersetter內部加了執行緒同步的鎖
    可以參考原始碼objc4的objc-accessors.mm,它並不能保證使用屬性的過程是執行緒安全的。
- (void)setName:(NSString *)name
{
    // 加鎖
    _name = name;
    // 解鎖
}
  • 為什麼說atomic不能保證使用屬性的過程是執行緒安全的?
    例如一顆可變陣列NSMutableArray,在呼叫settergetter是執行緒安全的,但是在新增addObject或刪除removeObject元素時並沒有新增鎖,所以不能保證在使用的時候是執行緒安全的。

iOS中的讀寫安全方案

  • 思考如何實現以下場景
    同一時間,只能有1個執行緒進行寫的操作;
    同一時間,允許有多個執行緒進行讀的操作;
    同一時間,不允許既有寫的操作,又有讀的操作。
  • 上面的場景就是典型的“多讀單寫”,經常用於檔案等資料的讀寫操作,iOS中的實現方案有:
    pthread_rwlock:讀寫鎖
    dispatch_barrier_async:非同步柵欄呼叫
1. pthread_rwlock 讀寫鎖

等待鎖的執行緒會進入休眠。需匯入#import <pthread.h>

 //初始化鎖
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, NULL);
    //讀加鎖
    pthread_rwlock_rdlock(&lock);
    //讀嘗試加鎖
    pthread_rwlock_tryrdlock(&lock);
    //寫加鎖
    pthread_rwlock_wrlock(&lock);
    //寫嘗試加鎖
    pthread_rwlock_trywrlock(&lock);
    //解鎖
    pthread_rwlock_unlock(&lock);
    //銷燬
    pthread_rwlock_destroy(&lock);
  • 示例
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化鎖
    pthread_rwlock_init(&_lock, NULL);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 100; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}
//讀
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    sleep(1);
    NSLog(@"%s--%@", __func__,[NSThread currentThread]);
    
    pthread_rwlock_unlock(&_lock);
}
//寫
- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    sleep(1);
    NSLog(@"%s--%@", __func__,[NSThread currentThread]);
    pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}
@end

寫總是單個列印,讀可能是多個同時列印,注意併發對弈必須是同一個佇列。

2. dispatch_barrier_async 非同步柵欄呼叫
  • 這個函式傳入的併發佇列必須是自己通過dispatch_queue_cretate建立的,如果傳入的是一個序列或是一個全域性的併發佇列,那這個函式便等同於dispatch_async函式的效果。
//手動建立併發佇列
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
//讀操作
dispatch_async(queue, ^{
        //執行讀任務
        });
//寫操作  
dispatch_barrier_async(queue, ^{
            //執行寫任務
        });      
  • 示例:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });

        dispatch_async(queue, ^{
            [self read];
        });

        dispatch_async(queue, ^{
            [self read];
        });

        dispatch_barrier_async(queue, ^{
            [self write];
        });
        dispatch_barrier_async(queue, ^{
            [self write];
        });
    }
}
- (void)read {
    sleep(2);
    static int a = 0;
    a++;
    NSLog(@"read:%d次",a);
}
- (void)write
{
    sleep(2);
    static int a = 0;
    a++;
    NSLog(@"write:%d次",a);
}
@end

不管是讀寫鎖還是柵欄函式,在讀的時候是可以多執行緒併發執行,但在寫的時候只能同步執行。

面試題

  • 你理解的多執行緒?
    開啟多條執行緒同時執行不同的任務,可以提升任務執行的效率,把耗時操作放到子執行緒執行,可以讓主執行緒

  • iOS的多執行緒方案有哪幾種?你更傾向於哪一種?
    pthreadNSThreadGCDNSOperation,更傾向於後面兩種,因為使用簡單,系統自己管理記憶體。

  • 你在專案中用過 GCD 嗎?
    用過,比如GCD定時器,GCD處理網路請求資料。

  • GCD 的佇列型別
    序列佇列,併發佇列,主佇列。

  • 說一下 OperationQueue 和 GCD 的區別,以及各自的優勢?

  • 執行緒安全的處理手段有哪些?
    互斥鎖,自旋鎖,GCD可以設定訊號量位1,序列佇列,OperationQueue可以設定最大併發鎖為1等等都可以達到執行緒同步,從而保證了執行緒安全。

  • OC你瞭解的鎖有哪些?在你回答基礎上進行二次提問;

  • 追問一:自旋和互斥對比?

  • 追問二:使用以上鎖需要注意哪些?

  • 追問三:用C/OC/C++,任選其一,實現自旋或互斥?口述即可!

相關推薦

iOS底層原理執行

簡介 進階 GCD 0GCD的常用函式: GCD中有2個用來執行任務的函式 用同步的方式執行任務 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); 用非同步的方式執行任務 di

iOS經典講解執行應用場景

<pre name="code" class="objc"> 通過下面一個例子來了解一下多執行緒的應用場景,我們可以通過點選按鈕來開始或者暫停動畫的播放,但是當我們點選另一個按鈕時, 就會執行一個方法,在該方法中迴圈列印一個很大的數字,在列印過程中,再通過點

Java基礎執行原理、實現方式及匿名內部類建立執行方法

一、概念 程序:作業系統當中正在執行的一個程式。例如正在執行一個QQ。 執行緒:程序之內多工的執行單位。例如迅雷當中正在下載的多個電影。 JVM當中:棧(Stack)記憶體是執行緒獨立的,堆(Heap)記憶體是執行緒共享的。 (1)Java程式執行的時候至少有兩個執行緒: 1)主

Java執行記憶體可見性_2(synchronized可見性原理)

可見性:要實現共享變數的可見性,必須保證2點:1.執行緒修改後的共享變數值能夠及時從工作記憶體重新整理到主記憶體中。2.其他執行緒能夠及時把共享變數的最新值從主記憶體更新到自己的工作記憶體中。 以下的記錄都是來源於慕課網-細說java多執行緒之記憶體可見性 Java語言層

iOS開發執行NSThread

之前的文章中介紹,多執行緒能夠提高應用程式的執行效率,把耗時的操作放在子執行緒中執行,這樣不會阻塞主執行緒的執行,不會給使用者“卡”的體驗。本文主要介紹多執行緒程式設計的第一種方式。 由於pthread是底層的多執行緒程式設計API,對於pthread,瞭解如何讓建立子執行

ios開發進階執行01 執行 GCD

一 多執行緒基礎 什麼是程序? 程序是指在系統中正在執行的一個應用程式。 每個程序之間是獨立的,每個程序均執行在其專用且受保護的記憶體空間內。 什麼是執行緒? 1個程序要想執行任務,必須得有執行緒(每1個程序至少要有1條執行緒)。 1個執行緒中任務的執行

Objective-C高階程式設計:iOS與OS X執行和記憶體管理

這篇文章主要給大家講解一下GCD的平時不太常用的API,以及文末會貼出GCD定時器的一個小例子。 需要學習的朋友可以通過網盤免費下載pdf版 (先點選普通下載-----再選擇普通使用者就能免費下載了)http://putpan.com/fs/cy1i1beebn7s0h4u9/ 1.G

Python爬蟲執行程序

前言 我們之前寫的爬蟲都是單個執行緒的?這怎麼夠?一旦一個地方卡到不動了,那不就永遠等待下去了?為此我們可以使用多執行緒或者多程序來處理。 首先宣告一點! 多執行緒和多程序是不一樣的!一個是 thread 庫,一個是 multiprocessing 庫。而多執行緒 thread 在 Pytho

java面試/筆試題目執行及鎖 (持續更新中)

前言:這一模組可以參照徐劉根大佬的部落格。 一.執行緒和程序的概念、並行和併發的概念 1.程序:是計算機中的程式關於某資料集合上的一次執行活動,是系統 進行資源分配和排程的基本單位,是作業系統結構的基礎。程式是指令、資料及其組織形式的描述,程序是程式的實體。 2.執行緒:是程式執行流的

[讀書筆記]iOS與OS X執行和記憶體管理 [GCD部分]

3.2 GCD的API 蘋果對GCD的說明:開發者要做的只是定義想執行的任務並追加到適當的Dispatch Queue中。 “Dispatch Queue”是執行處理的等待佇列。通過dispatch_async函式等API,在Block

併發程式設計執行執行安全

什麼是執行緒安全? 為什麼有執行緒安全問題? 當多個執行緒同時共享,同一個全域性變數或靜態變數,做寫的操作時,可能會發生資料衝突問題,也就是執行緒安全問題。但是做讀操作是不會發生資料衝突問題。 案例: 需求現在有100張火車票,有兩個視窗同時搶火車票,請使用多執行緒模擬搶票效果。 p

併發程式設計執行基礎

執行緒與程序區別 每個正在系統上執行的程式都是一個程序。每個程序包含一到多個執行緒。執行緒是一組指令的集合,或者是程式的特殊段,它可以在程式裡獨立執行。也可以把它理解為程式碼執行的上下文。所以執行緒基本上是輕量級的程序,它負責在單個程式裡執行多工。通常由作業系統負責多個執行緒的排程和執行。

2018-08-28微服務筆記(一)執行

1.多執行緒 1.1 程序與執行緒 (1)程序:正在執行的程式,是執行緒的集合。主執行緒決定程式碼的執行順序。 (2)執行緒:正在獨立執行的一條執行路徑。 (3)多執行緒:為了提高程式的效率。 1.2 四種方式建立執行緒 (1)繼承Thread類 (2)實現Runnable介面

iOS - 知識梳理(執行

多執行緒:一個程序裡面開啟多條執行緒,每條執行緒可以單獨的執行不同的任務。 iOS實現多執行緒的方式: 1、pthread(C寫的、基本不用) 2、NSThread 3、gcd 4、NSOperation 下面分別介紹下後三個常用的多執行緒方式 NSThread: 使用方式

.NET基礎執行開發基礎

轉自:http://www.cnblogs.com/edisonchou/p/4848131.html   多執行緒開發基礎  Index :  (1)型別語法、記憶體管理和垃圾回收基礎  (2)面向物件的實現和異常的處理基礎

學習彙集地,如果你擅長計算機組成原理執行,設計模式,jvm,前端或者其他都可以

擅長jvm 多執行緒 設計模式 資料庫 前端 分散式什麼的一起學習共同進步。   目的是大家在自學新的領域的時候有地方可以探討求疑 比如在看到垃圾回收各種收集器中遇到執行緒方面知識的時候 學習設計模式分不清單例和享元的區別,只有書本經驗不知道如何應用到實際開發的時候 學習資料庫系統

面試題——執行併發面試題

1) 什麼是執行緒?   執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程序之中,是程序中的實際運作單位。程式設計師可以通過它進行多處理器程式設計,你可以使用多執行緒對運算密集型任務提速。比如,如果一個執行緒完成一個任務要100毫秒,那麼用十個執行緒完成改任務只需10毫秒。Java在語言層面對多執行

面試題——執行詳解

多執行緒作為Java中很重要的一個知識點,在此還是有必要總結一下的。 一.執行緒的生命週期及五種基本狀態 關於Java中執行緒的生命週期,首先看一下下面這張較為經典的圖: 上圖中基本上囊括了Java中多執行緒各重要知識點。掌握了上圖中的各知識點,Java中的多執行緒也就基本上掌握了。主

JAVA複習執行

java中多執行緒同步是什麼? 在多執行緒程式下,同步能控制對共享資源的訪問。如果沒有同步,當一個java縣城在修改一個共享變數時,另外一個執行緒正在使用或者更新同一個變數,這樣容易導致程式出現錯誤。 解釋實現多執行緒的幾種方法?區別是什麼? Java執行緒可以實現Runnable介面或

Java基礎執行及併發庫

實際上關於多執行緒的基礎知識,前面自己已經總結過一部分,但是每一個階段對於同樣知識點的學習側重點是不一樣的,前面的Java基礎總結八之多執行緒(一)和 Java基礎總結九之多執行緒(二)是對JDK5以前多執行緒相關基礎知識的一個簡單總結,今天本文將偏重於JDK5提供的併發庫進行學習總結。 首先,