一. NSConditionLock
NSConditionLock是對NSCondition的進一步封裝,可以設置具體的條件值。
NSConditionLock相關API:
@interface NSConditionLock : NSObject <NSLocking> {
- (instancetype)initWithCondition:(NSInteger)condition;
@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;
@end
簡單使用如下:
#import "NSConditionLockDemo.h"
@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
}
return self;
}
- (void)otherTest
{
//線程1
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
//線程2
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
//線程3
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}
- (void)__one
{
[self.conditionLock lockWhenCondition:1];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
- (void)__two
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
- (void)__three
{
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
[self.conditionLock unlock];
}
@end
可以發現,三個子線程同時執行代碼,最后打印結果是:
__one
__two
__three
先執行線程1,再執行線程2,再執行線程3,達到線程3依賴線程2,線程2依賴線程1的效果。
使用場景:
如果子線程有依賴關系(子線程的執行是有順序的),就可以使用NSConditionLock,設置條件具體的值。
在GNUstep中查看源碼:
- (id) init
{
return [self initWithCondition: 0];
}
- (id) initWithCondition: (NSInteger)value
{
if (nil != (self = [super init]))
{
if (nil == (_condition = [NSCondition new]))
{
DESTROY(self);
}
else
{
_condition_value = value;
[_condition setName:
[NSString stringWithFormat: @"condition-for-lock-%p", self]];
}
}
return self;
}
可以發現兩個問題:
- 條件值默認是0
- NSConditionLock的確是對NSCondition的封裝
二. dispatch_queue(DISPATCH_QUEUE_SERIAL)
直接使用GCD的串行隊列,也是可以實現線程同步的
#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_sync函數的特點:要求立馬在當前線程同步執行任務(當前線程是子線程,在MJBaseDemo里面已經寫了)。
本來這個方法就是在子線程中執行的,把存錢、取錢操作放到一個串行隊列里面,把賣票操作放到另一個串行隊列里面。
舉例說明:比如線程4進來賣票,那么這個操作就會被放到串行隊列中,等一會線程7又進來賣票,這個操作也會被放到串行隊列中,串行隊列里面的東西是:線程4的賣票操作 - 線程7的賣票操作 - 線程5的賣票操作。
這樣線程4賣完,線程7賣,線程7賣完,線程5賣。這樣串行隊列中的任務是異步的,不會出現多個線程同時訪問一個成員變量的問題,這樣也能解決線程安全問題。
所以說,線程同步問題也不是必須要通過加鎖才能實現。
三. dispatch_semaphore
- semaphore叫做”信號量”
- 信號量的初始值,可以用來控制線程并發訪問的最大數量
- 信號量的初始值為1,代表同時只允許1條線程訪問資源,保證線程同步
如下代碼,創建15條線程,都調用test方法:
@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end
@implementation SemaphoreDemo
- (void)viewDidLoad {
[super viewDidLoad]
self.semaphore = dispatch_semaphore_create(5);
for (int i = 0; i < 15; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
// 線程10、7、6、9、8
- (void)test
{
// 如果信號量的值 > 0,就讓信號量的值減1,然后繼續往下執行代碼
// 如果信號量的值 <= 0,就會休眠等待,直到信號量的值變成>0,就讓信號量的值減1,然后繼續往下執行代碼
// 第二個參數代表等到啥時候,傳入的DISPATCH_TIME_FOREVER,代表一直等
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 讓信號量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@end
打印:
test - <NSThread: 0x600003a59380>{number = 7, name = (null)}
test - <NSThread: 0x600003a59400>{number = 9, name = (null)}
test - <NSThread: 0x600003a593c0>{number = 8, name = (null)}
test - <NSThread: 0x600003a59440>{number = 10, name = (null)}
test - <NSThread: 0x600003a59140>{number = 4, name = (null)}
間隔2秒
test - <NSThread: 0x600003a594c0>{number = 12, name = (null)}
test - <NSThread: 0x600003a59480>{number = 11, name = (null)}
test - <NSThread: 0x600003a59500>{number = 13, name = (null)}
test - <NSThread: 0x600003a59300>{number = 5, name = (null)}
test - <NSThread: 0x600003a59540>{number = 14, name = (null)}
間隔2秒
test - <NSThread: 0x600003a59580>{number = 15, name = (null)}
test - <NSThread: 0x600003a595c0>{number = 16, name = (null)}
test - <NSThread: 0x600003a59600>{number = 17, name = (null)}
test - <NSThread: 0x600003a59340>{number = 6, name = (null)}
test - <NSThread: 0x600003a59100>{number = 3, name = (null)}
上面代碼,創建了15條線程,如果不控制線程并發訪問的最大數量,那么有可能15條線程同時訪問test方法,這樣就不安全。使用了信號量,發現最多只有5條線程訪問test,這5條訪問完,后面5條線程繼續訪問。
所以,如果信號量的初始值為1,代表同時只允許1條線程訪問資源,保證線程同步,代碼如下:
#import "SemaphoreDemo.h"
@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end
@implementation SemaphoreDemo
- (instancetype)init
{
if (self = [super init]) {
//設置信號量的初始值為1,代表同時只允許1條線程訪問資源,保證線程同步
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);
}
信號量原理:
dispatch_semaphore_t ticketSemaphore = dispatch_semaphore_create(1);
//如果信號量的值 > 0,就讓信號量的值減1,然后繼續往下執行代碼
//如果信號量的值 <= 0,就會休眠等待,直到信號量的值變成>0,就讓信號量的值減1,然后繼續往下執行代碼
//第二個參數代表等到啥時候,傳入的DISPATCH_TIME_FOREVER,代表一直等
dispatch_semaphore_wait(ticketSemaphore, DISPATCH_TIME_FOREVER);
......
//讓信號量的值+1
dispatch_semaphore_signal(ticketSemaphore);
小提示:控制線程并發訪問的最大數量也可以用
NSOperationQueue *queue;
queue.maxConcurrentOperationCount = 5;
四. @synchronized
- @synchronized是對mutex遞歸鎖的封裝
- @synchronized(obj)內部會生成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
{
//除了用類對象作為一把鎖,也可以直接傳入一個其他對象,并且要dispatch_once_t,保證以后不管調用多少次都是同一個對象。
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});
@synchronized(lock) {
[super __saleTicket];
}
}
@end
在@synchronized處打斷點,查看匯編,可以發現會調用objc_sync_enter和objc_sync_exit。就相當于,第一個“{”是objc_sync_enter,第二個“}”是objc_sync_exit,如下:
@synchronized([self class]) { // objc_sync_enter
......
} // objc_sync_exit
然后在objc4中的objc-sync.mm文件中查看objc_sync_enter函數的源碼實現:
typedef struct SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount;
recursive_mutex_t mutex; //遞歸鎖
} SyncData;
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//將傳進來的obj轉成data
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();//再從data里面取出一把遞歸鎖,所以只要obj一樣,取出的鎖也一樣
} else {
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
從上面代碼可以看出,將傳進來的obj轉成data,再從data里面取出一把遞歸鎖,所以只要obj一樣,取出的鎖也一樣,所以我們上面才說傳入的obj要唯一。
遞歸鎖:允許同一個線程對一把鎖進行重復加鎖(解鎖)
既然@synchronized是遞歸鎖,那么肯定可以做遞歸鎖可以做的事情,如下:
- (void)otherTest {
@synchronized([self class]) {
NSLog(@"123");
//遞歸調用10次
static int count = 0;
if (count < 10) {
count++;
[self otherTest];
}
}
}
上面代碼,遞歸調用10次,最后打印10次“123”。如果將@synchronized換成其他鎖,遞歸調用就會造成死鎖,最后結果打印了10次,說明的確是遞歸鎖。
注意:@synchronized和@synthesize、@dynamic不一樣,別弄混淆了,關于@synthesize、@dynamic可參考Runtime3-objc_msgSend底層調用流程的補充內容。
五. 總結:回憶一下前面學的各種鎖
OSSpinLock 自旋鎖,因為底層是使用while循環進行忙等,不會進行休眠和喚醒,所以是性能比較高的一把鎖,但是現在已經不安全,被拋棄。
os_unfair_lock 用于取代不安全的OSSpinLock ,從iOS10開始才支持。從底層調用看,等待os_unfair_lock鎖的線程會處于休眠狀態,并非忙等,是一種互斥鎖。
pthread_mutex mutex叫做”互斥鎖”,等待鎖的線程會處于休眠狀態。
它是跨平臺的,當傳入的類型是默認的就是默認鎖,當傳入PTHREAD_MUTEX_RECURSIVE,就是遞歸鎖,還可以通過pthread_cond_wait(&_cond, &_mutex)當做條件鎖來使用。
dispatch_semaphore 信號量的初始值為1,代表同時只允許1條線程訪問資源,保證線程同步
dispatch_queue(DISPATCH_QUEUE_SERIAL) 直接使用GCD的串行隊列,也是可以實現線程同步的
NSLock是對mutex普通鎖的封裝
NSRecursiveLock是對mutex遞歸鎖的封裝,API跟NSLock基本一致
NSCondition是對mutex和cond的封裝
NSConditionLock是對NSCondition的進一步封裝,可以設置具體的條件值
@synchronized也是對mutex遞歸鎖的封裝
@synchronized(obj)內部會生成obj對應的遞歸鎖,然后進行加鎖、解鎖操作
六. 各種鎖性能比較
自此,iOS中各種鎖基本上都講完了,下面比較一下性能,然后找出一個最好的,留著開發中使用。
性能從高到低排序:
os_unfair_lock iOS10開始支持
OSSpinLock 不安全,被拋棄
dispatch_semaphore 如果需要iOS8、9都支持可以使用
pthread_mutex 可以跨平臺
dispatch_queue(DISPATCH_QUEUE_SERIAL) 本來GCD效率就很高
NSLock 對mutex普通鎖的封裝
NSCondition 對mutex和cond的封裝
pthread_mutex(recursive) mutex遞歸鎖,遞歸鎖效率本來就低
NSRecursiveLock 對mutex遞歸鎖的封裝
NSConditionLock 對NSCondition的封裝
@synchronized 對mutex遞歸鎖的封裝
總結:
一般推薦使用os_unfair_lock、dispatch_semaphore、pthread_mutex。
使用技巧:
對于dispatch_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);
使用如下:
- (void)test1
{
SemaphoreBegin;
// .....
SemaphoreEnd;
}
- (void)test2
{
SemaphoreBegin;
// .....
SemaphoreEnd;
}
- (void)test3
{
SemaphoreBegin;
// .....
SemaphoreEnd;
}
這樣就保證每個方法內部的鎖都不一樣,同時使用起來也很簡單。
如果每個方法的鎖都是一樣的,那就只能把鎖寫到外面去了。
同理,pthread_mutex也可以這樣封裝。
七. 面試題
你理解的多線程?
就是多條線程同時做事情,優點就是效率高,缺點就是可能會造成線程安全問題。iOS的多線程方案有哪幾種?你更傾向于哪一種?
多線程方案如下圖,更傾向于GCD。
① 這些多線程方案的底層都是依賴pthread
② NSThread線程生命周期是程序員管理,GCD和NSOperation是系統自動管理
③ NSThread和NSOperation都是OC的,更加面向對象
④ NSOperation基于CGD,使用更加面向對象
你在項目中用過 GCD 嗎?
GCD 的隊列類型?
說一下 OperationQueue 和 GCD 的區別,以及各自的優勢有哪些?
線程安全的處理手段有哪些?
上面講的OC你了解的鎖有哪些?在你回答基礎上進行二次提問;
追問一:自旋和互斥對比?
追問二:使用以上鎖需要注意哪些?
追問三:用C/OC/C++,任選其一,實現自旋或互斥?口述即可!自旋鎖、互斥鎖比較?
自旋鎖:顧名思義就是自己一直在旋轉的鎖,比如上面的OSSpinLock,它底層是個while循環。
互斥鎖:互斥鎖提供一個可以在同一時間,只讓一個線程訪問臨界資源的操作接口。互斥鎖(Mutex)是個提供線程同步的基本鎖。上鎖后,其他的線程如果想要鎖上,那么會被阻塞(線程休眠),直到鎖釋放后(說明,一般會把訪問共享內存這段代碼放在上鎖程序之后)———百度百科
上面講的那些鎖,除了OSSpinLock是自旋鎖,其他的都是互斥鎖。
什么情況使用自旋鎖比較劃算?
預計線程等待鎖的時間很短
加鎖的代碼(臨界區)經常被調用,但競爭情況很少發生
CPU資源不緊張
多核處理器什么情況使用互斥鎖比較劃算?
預計線程等待鎖的時間較長
單核處理器
臨界區有IO操作,因為IO操作比較占用CPU資源
臨界區代碼復雜或者循環量大
臨界區競爭非常激烈
八. atomic
nonatomic和atomic
atom:原子,不可再分割的單位
atomic:原子性
原子性操作就說明這個操作是個整體,不可分割的。
比如如下三行代碼,如果不是原子性操作,那么這三行代碼就有可能被三個線程執行,這樣肯定是不行的,那怎么變成原子性操作呢?
在第一行前面加鎖,第三行后面解鎖,就變成了原子性操作,變成原子性操作之后要么不執行,要執行必須把三行一塊執行完,如下:
// 加鎖
int a = 10;
int b = 20;
int c = a + b;
// 解鎖
atomic用于保證屬性setter、getter的原子性操作,相當于在getter和setter內部加了線程同步的鎖,也就是setter和getter內部都有加鎖操作。
在objc4的objc-accessors.mm文件找到如下源碼:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) { //如果是nonatomic,就直接賦值
oldValue = *slot;
*slot = newValue;
} else { //如果是atomic,就先加鎖后解鎖
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock(); //加鎖
oldValue = *slot;
*slot = newValue;
slotlock.unlock(); //解鎖
}
objc_release(oldValue);
}
可以發現,setter方法里面,如果是nonatomic,就直接賦值,如果是atomic,就先加鎖后解鎖,getter方法也一樣,就不解釋了。
對于atomic,上面我們說了setter、getter方法內部會有加鎖、解鎖操作,但它并不能保證使用屬性的過程是線程安全的,什么意思呢?
如下:
@interface MJPerson : NSObject
@property (strong, atomic) NSMutableArray *data; //atomic修飾
@end
//執行以下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *p = [[MJPerson alloc] init];
for (int i = 0; i < 10; i++) {
dispatch_async(NULL, ^{
p.data = [NSMutableArray array];
});
}
NSMutableArray *array = p.data;
// 沒有加鎖
[array addObject:@"1"];
[array addObject:@"2"];
[array addObject:@"3"];
// 沒有解鎖
}
return 0;
}
上面代碼 p.data = [NSMutableArray array] 這一行是線程安全的,因為它的setter方法內部有加鎖、解鎖操作。
但是 [array addObject:@"1"] 這一行就不是線程安全的了,因為它沒有加鎖、解鎖操作。
既然atomic是線程安全的,那么為什么開發中我們基本不用呢?
太耗性能了,因為setter、getter方法調用次數太頻繁了,如果每次都需要加鎖、解鎖,那手機CPU資源不就被你消耗完了。所以atomic一般在MAC上才使用。
而且只有多條線程同時訪問同一個對象的屬性,才會有線程安全問題。這種情況幾乎沒有,如果你非要造出來這種情況,比如如下代碼,多條線程同時訪問 p.data ,那你完全可以在外面加鎖嘛!
for (int i = 0; i < 10; i++) {
dispatch_async(NULL, ^{
// 在外面加鎖
p.data = [NSMutableArray array];
// 在外面解鎖
});
}
九. 讀寫安全方案
顯然,如果使用上面講的加鎖方案,那么無論讀、寫,同一時間只有一條線程在執行,這樣效率比較低,實際上讀操作可以同時多條線程一起執行的。
思考如何實現以下場景
同一時間,只能有1個線程進行寫的操作
同一時間,允許有多個線程進行讀的操作
同一時間,不允許既有寫的操作,又有讀的操作
上面的場景就是典型的“多讀單寫”,經常用于文件等數據的讀寫操作,iOS中的實現方案有:
- pthread_rwlock_t:讀寫鎖
- dispatch_barrier_async:異步柵欄調用
1. pthread_rwlock_t
等待鎖的線程會進入休眠(有點互斥鎖的感覺)
代碼如下:
#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 < 10; i++) {
//讀
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self read];
});
//寫
dispatch_async(queue, ^{
[self write];
});
dispatch_async(queue, ^{
[self write];
});
dispatch_async(queue, ^{
[self write];
});
}
}
- (void)read {
//讀-嘗試加鎖
//pthread_rwlock_tryrdlock(&_lock);
//讀-加鎖
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"%s--線程:%@", __func__,[NSThread currentThread]);
//解鎖
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
//寫-嘗試加鎖
//pthread_rwlock_trywrlock(&_lock);
//寫-加鎖
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"%s--線程:%@", __func__,[NSThread currentThread]);
//解鎖
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
//銷毀
pthread_rwlock_destroy(&_lock);
}
@end
打印:
2021-06-01 15:41:18.160200+0800 -[read]--線程:<NSThread: 0x600000356780>{number = 4, name = (null)}
2021-06-01 15:41:18.160212+0800 -[read]--線程:<NSThread: 0x600000374a40>{number = 7, name = (null)}
2021-06-01 15:41:19.162515+0800 -[write]--線程:<NSThread: 0x600000351c80>{number = 3, name = (null)}
2021-06-01 15:41:20.165942+0800 -[read]--線程:<NSThread: 0x600000341640>{number = 5, name = (null)}
2021-06-01 15:41:21.166782+0800 -[write]--線程:<NSThread: 0x600000371a40>{number = 8, name = (null)}
2021-06-01 15:41:22.172355+0800 -[write]--線程:<NSThread: 0x600000365d80>{number = 9, name = (null)}
2021-06-01 15:41:23.176932+0800 -[read]--線程:<NSThread: 0x600000365d80>{number = 10, name = (null)}
2021-06-01 15:41:23.176946+0800 -[read]--線程:<NSThread: 0x600000372e40>{number = 11, name = (null)}
2021-06-01 15:41:23.176946+0800 -[read]--線程:<NSThread: 0x600000378e00>{number = 12, name = (null)}
2021-06-01 15:41:24.182928+0800 -[write]--線程:<NSThread: 0x600000364700>{number = 13, name = (null)}
2021-06-01 15:41:25.188879+0800 -[write]--線程:<NSThread: 0x600000365fc0>{number = 14, name = (null)}
2021-06-01 15:41:26.189801+0800 -[write]--線程:<NSThread: 0x600000364b00>{number = 15, name = (null)}
2021-06-01 15:41:27.194003+0800 -[read]--線程:<NSThread: 0x600000378dc0>{number = 18, name = (null)}
2021-06-01 15:41:27.193997+0800 -[read]--線程:<NSThread: 0x600000365dc0>{number = 17, name = (null)}
2021-06-01 15:41:27.193997+0800 -[read]--線程:<NSThread: 0x600000374480>{number = 16, name = (null)}
2021-06-01 15:41:28.194641+0800 -[write]--線程:<NSThread: 0x600000372e40>{number = 19, name = (null)}
2021-06-01 15:41:29.198154+0800 -[write]--線程:<NSThread: 0x600000365a40>{number = 20, name = (null)}
2021-06-01 15:41:30.203043+0800 -[write]--線程:<NSThread: 0x6000003746c0>{number = 21, name = (null)}
2021-06-01 15:41:31.208880+0800 -[read]--線程:<NSThread: 0x600000365ec0>{number = 22, name = (null)}
2021-06-01 15:41:31.208880+0800 -[read]--線程:<NSThread: 0x600000374ac0>{number = 24, name = (null)}
2021-06-01 15:41:31.208874+0800 -[read]--線程:<NSThread: 0x600000372940>{number = 23, name = (null)}
2021-06-01 15:41:32.209671+0800 -[write]--線程:<NSThread: 0x600000374e80>{number = 25, name = (null)}
2021-06-01 15:41:33.215514+0800 -[write]--線程:<NSThread: 0x600000374940>{number = 26, name = (null)}
2021-06-01 15:41:34.221247+0800 -[write]--線程:<NSThread: 0x600000365c00>{number = 27, name = (null)}
2021-06-01 15:41:35.227011+0800 -[read]--線程:<NSThread: 0x600000372940>{number = 29, name = (null)}
2021-06-01 15:41:35.227010+0800 -[read]--線程:<NSThread: 0x6000003746c0>{number = 28, name = (null)}
2021-06-01 15:41:35.227011+0800 -[read]--線程:<NSThread: 0x600000378e40>{number = 30, name = (null)}
2021-06-01 15:41:36.231641+0800 -[write]--線程:<NSThread: 0x600000372e00>{number = 31, name = (null)}
2021-06-01 15:41:37.237418+0800 -[write]--線程:<NSThread: 0x600000365540>{number = 32, name = (null)}
2021-06-01 15:41:38.243212+0800 -[write]--線程:<NSThread: 0x600000372fc0>{number = 33, name = (null)}
可以發現,讀同時進行,寫就不能同時進行了。
2. dispatch_barrier_async
廢話少說,先看怎么使用,如下:
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//手動創建并發隊列
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
//讀
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
//寫
//當有一條線程在執行這個任務的時候,絕不允許queue中有其他線程在執行其他任務(包括上面的read和下面的write)
dispatch_barrier_async(self.queue, ^{
[self write];
});
dispatch_barrier_async(self.queue, ^{
[self write];
});
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
}
- (void)read {
sleep(1);
NSLog(@"%s--線程:%@", __func__,[NSThread currentThread]);
}
- (void)write
{
sleep(1);
NSLog(@"%s--線程:%@", __func__,[NSThread currentThread]);
}
@end
打印如下:
2021-06-01 16:00:12.233462+0800 -[read]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:12.233472+0800 -[read]--線程:<NSThread: 0x60000144c240>{number = 3, name = (null)}
2021-06-01 16:00:12.233479+0800 -[read]--線程:<NSThread: 0x600001445d80>{number = 5, name = (null)}
2021-06-01 16:00:13.237569+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:14.239756+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:15.243263+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:16.248324+0800 -[read]--線程:<NSThread: 0x60000144c240>{number = 3, name = (null)}
2021-06-01 16:00:16.248324+0800 -[read]--線程:<NSThread: 0x600001445d80>{number = 5, name = (null)}
2021-06-01 16:00:16.248324+0800 -[read]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:17.253537+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:18.256256+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:19.257152+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:20.262030+0800 -[read]--線程:<NSThread: 0x600001445d80>{number = 5, name = (null)}
2021-06-01 16:00:20.262026+0800 -[read]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:20.262049+0800 -[read]--線程:<NSThread: 0x600001445b00>{number = 7, name = (null)}
2021-06-01 16:00:21.262937+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:22.268470+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:23.273291+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:24.279084+0800 -[read]--線程:<NSThread: 0x600001445b00>{number = 7, name = (null)}
2021-06-01 16:00:24.279085+0800 -[read]--線程:<NSThread: 0x600001445d80>{number = 5, name = (null)}
2021-06-01 16:00:24.279085+0800 -[read]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:25.281132+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:26.282865+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:27.286220+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:28.290012+0800 -[read]--線程:<NSThread: 0x600001445d80>{number = 5, name = (null)}
2021-06-01 16:00:28.290012+0800 -[read]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:28.290012+0800 -[read]--線程:<NSThread: 0x600001445b00>{number = 7, name = (null)}
2021-06-01 16:00:29.290888+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:30.296537+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
2021-06-01 16:00:31.301259+0800 -[write]--線程:<NSThread: 0x60000147ab80>{number = 6, name = (null)}
可以發現,打印結果和讀寫鎖一樣,讀同時進行,寫就不能同時進行,說明dispatch_barrier_async在文件IO操作中也是有用的。
那么是如何做到的呢?
- 首先,對于讀操作,使用dispatch_async,所以讀操作是異步的。
- 對于寫操作,使用dispatch_barrier_async,保證當有一條線程在執行這個任務的時候,絕不允許queue中有其他線程在執行其他任務。
示意圖如下:
注意:這個dispatch_barrier_async函數傳入的并發隊列必須是自己手動通過dispatch_queue_cretate創建的。如果傳入的是一個串行或是一個全局的并發隊列,那這個函數便等同于dispatch_async函數的效果。
dispatch_barrier_async和dispatch_barrier_sync的區別
相同點:使用dispatch_barrier_async和dispatch_barrier_sync,都保證當有一條線程在執行這個任務的時候,絕不允許queue中有其他線程在執行其他任務。
不同點:dispatch_barrier_async是異步柵欄,會開啟新線程(如上打印所示),dispatch_barrier_sync是同步柵欄,不會開啟新線程。
如果把上面代碼改成:
dispatch_barrier_sync(self.queue, ^{
[self write];
});
打印如下:
2021-06-01 16:20:51.992188+0800 -[read]--線程:<NSThread: 0x6000034107c0>{number = 4, name = (null)}
2021-06-01 16:20:51.992205+0800 -[read]--線程:<NSThread: 0x6000034121c0>{number = 5, name = (null)}
2021-06-01 16:20:51.992208+0800 -[read]--線程:<NSThread: 0x600003401cc0>{number = 6, name = (null)}
2021-06-01 16:20:52.993967+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:20:53.995608+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:20:54.996736+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:20:56.001371+0800 -[read]--線程:<NSThread: 0x6000034121c0>{number = 5, name = (null)}
2021-06-01 16:20:56.001470+0800 -[read]--線程:<NSThread: 0x600003401cc0>{number = 6, name = (null)}
2021-06-01 16:20:56.001486+0800 -[read]--線程:<NSThread: 0x6000034107c0>{number = 4, name = (null)}
2021-06-01 16:20:57.002200+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:20:58.003470+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:20:59.004165+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:00.005287+0800 -[read]--線程:<NSThread: 0x600003401cc0>{number = 6, name = (null)}
2021-06-01 16:21:00.005287+0800 -[read]--線程:<NSThread: 0x6000034107c0>{number = 4, name = (null)}
2021-06-01 16:21:00.005287+0800 -[read]--線程:<NSThread: 0x6000034121c0>{number = 5, name = (null)}
2021-06-01 16:21:01.005890+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:02.006747+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:03.008123+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:04.009767+0800 -[read]--線程:<NSThread: 0x6000034121c0>{number = 5, name = (null)}
2021-06-01 16:21:04.009768+0800 -[read]--線程:<NSThread: 0x600003401cc0>{number = 6, name = (null)}
2021-06-01 16:21:04.009781+0800 -[read]--線程:<NSThread: 0x60000341fac0>{number = 3, name = (null)}
2021-06-01 16:21:05.010196+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:06.010984+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:07.011930+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:08.014189+0800 -[read]--線程:<NSThread: 0x6000034121c0>{number = 5, name = (null)}
2021-06-01 16:21:08.014189+0800 -[read]--線程:<NSThread: 0x600003401cc0>{number = 6, name = (null)}
2021-06-01 16:21:08.014190+0800 -[read]--線程:<NSThread: 0x6000034107c0>{number = 4, name = (null)}
2021-06-01 16:21:09.015798+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:10.016433+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
2021-06-01 16:21:11.017820+0800 -[write]--線程:<NSThread: 0x600003454980>{number = 1, name = main}
可以發現,使用dispatch_barrier_sync沒有開啟新線程,寫操作在當前線程(主線程)執行。
Demo地址:加鎖方案2