四.併發編程

四.併發編程

  • 建立和管理線程
  • 多線程優化技術(GCD)
  • 操做和隊列

4.1 線程

線程時運行時執行的一組指令序列,每一個進程至少包含一個線程。在iOS中,進程啓動時的主要線程稱做主線程。全部UI元素都須要在主線程中建立和管理。html

4.2 線程開銷

每一個線程都有必定的開銷,從而影響到應用的性能。線程不只僅有建立時的時間開銷,還會消耗內核的內存,即應用的內存空間。git

4.2.1 內核數據結構

每一個線程大約消耗1KB的內核內存空間。這塊內存用於存儲於線程有關的數據結構和屬性。這塊內存時聯動內存(聯動內存(Wired Memory)中的內容必須保留在內存中而不能被移動到磁盤或其餘外部存儲中,聯動內存的容量取決於當前使用的應用軟件)。github

4.2.2 棧空間

主線程的棧空間大小爲1M,並且沒法修改。全部的二級線程默認分配512KB的棧空間。 在線程啓動前,棧空間的大小能夠被改變。棧空間的最小值是16KB,並且其數值必須是4KB的倍數。編程

//修改棧空間
+(NSThread *)createThreadWithTarget:(id)target selector:(SEL)selector object:(id)argument stackSize:(NSUInteger)size{
	if ((size % 4096) != 0 ){
		return nil;
	}
	
	NSThread *t = [[NSThread alloc]initWithTarget:target selector:selector object:argument];
	t.stackSize = size;
	
	return t;
}

4.3 GCD

GCD API:https://developer.apple.com/documentation/dispatch?language=objc GCD提供的功能列表:swift

  • 任務或分發隊列,容許主線程中執行,並行執行和串行執行。
  • 分發組,實現對一組任務執行狀況的跟蹤,而與這些任務所基於的隊列無關
  • 信號量
  • 屏障,容許在並行分發隊列中建立同步的點
  • 分發對象和管理源,實現更爲底層的管理和監控
  • 異步I/O,使用文件描述或管道

GCD一樣解決了線程的建立和管理,它幫助咱們跟蹤應用中的線程的總數,且不會形成任何泄露。緩存

大多數狀況下,應用單獨使用GCD就能夠很好的工做,但仍有特定的狀況須要考慮使用NSThread或NSOperationQueue。當應用中有多個長耗時的任務須要並行執行時,最好,對線程的建立過程加以口控制。若是代碼執行的時間過長,頗有可能達到線程的限制64個,即GCD的線程池上限安全

4.4 操做與隊列

NSOperation封裝了一個任務以及和任務相關的數據和代碼,而NSOperationQueue以先入先出的順序控制了一個或多個這類任務的執行。 NSOperation和NSOperation都提供了控制線程個數的能力。可用maxConcurrentOperation屬性控制隊列的個數,也能夠控制每一個隊列的線程個數。 NSThread,NSOperation和GCD API的快速比較:服務器

GCDcookie

  • 抽象程度最高。
  • 兩種隊列開箱即用:main和global。
  • 能夠建立更多的隊列(使用dispatch_queue_create)。
  • 能夠請求獨佔訪問(使用dispatch_barrier_sync和dispatch_barrier_async)。
  • 基於線程管理。
  • 硬性限制建立64個線程。

NSOperation網絡

  • 無默認隊列。
  • 應用管理本身建立的隊列。
  • 隊列是優先級隊列。
  • 操做能夠有不一樣的優先級(使用queuePriority屬性)。
  • 使用cancel消息能夠取消操做。注意,cancel僅僅是個標記。若是操做已經開始執行,則可能會繼續執行下去。
  • 能夠等待某個操做執行完畢(使用waitUnitilFinished消息)。

NSThread

  • 低級別構造,最大化控制。
  • 應用建立並管理線程。
  • 應用建立並管理線程池。
  • 應用啓動線程。
  • 線程能夠擁有優先級,操做西永會根據優先級調度他們的執行。
  • 無直接API用於等待線程完成。須要使用互斥量(如NSLock)和自定義代碼。

NSOperation是多核安全的。能夠放心地分享隊列,從不一樣的線程中提交任務,而無需擔憂損壞隊列

4.5 線程安全的代碼

4.5.1 原子屬性

@property(atomic) NSString *atomic;//原子屬性
@property(nonatomic) NSString *Nonatomic;//非原子屬性

由於原子屬性存在開銷,因此過分使用它們並不明智。若是能保證某個屬性在任什麼時候刻都不會被多個線程訪問,最好仍是標記爲nonatomic。

4.5.2 同步塊

原子屬性並不能保證代碼必定是線程安全的。 全部相關的狀態都應該再同一個事物中批量更新。 使用@synchronized指令能夠建立一個信號量,並進入臨界區,臨界區在任什麼時候刻都只能被一個線程執行。

-(void)updateUser:(HPUser *)user properties:(NSDictionary *)properties{
	@synchronized(user){//取得針對user對象的鎖。一切相關的修改都會被一同處理,而不會發生競爭狀態。
		NSString *fn = [properties objectForKey:@"firstName"];
		if (fn != nil){
			user.firstName = fn;
		}
		NSString *ln = [properties objectForKey:@"lastName"];
		if (ln != nil){
			user.lastName = ln;
		}
	}
}

過渡使用@synchronized指令會拖慢應用的運行速度,由於任什麼時候間都只有一個線程在臨界區內執行。 經過uesr對象獲取鎖。所以updateUser: properties:方法能夠從多個線程被調用,不一樣的用戶用不一樣的線程。當user對象不一樣時,該方法仍然可以高併發地執行。 獲取鎖的對象是良好定義的臨界區的關鍵。做爲經驗法則,能夠選擇狀態會被訪問和修改的對象做爲信號量的引用

4.5.3 鎖

鎖是進入臨界區的基礎構件。atomic屬性和@synchronized塊是爲了實現便捷實用的高級別抽象。

  • NSLock
@interface ThreadSafeClass : NSObject
{
	NSLock *lock;
}
@end

@implementation ThreadSafeClass
-(instancetype)init{
	if (self = [super init]){
		self -> lock = [NSLock new];
	}
	return self;
}
-(void)safeMethod{
	[self -> lock lock];//獲取鎖,進入臨界區
	
	//線程安全代碼,在臨界區,任意時刻最多隻容許一個線程執行
	
	[self -> lock unlock];//釋放鎖標記着臨界區的結束。其餘線程如今可以獲取鎖了。
}
@end
  • NSRecursiveLock NSRecursiveLock容許在被解鎖前鎖定屢次。若是解鎖的次數與鎖定的次數相匹配,則認爲鎖被釋放,其餘線程能夠獲取鎖。
@interface ThreadSafeClass : NSObject
{
	NSRecursiveLock *rLock;
}
@end

@implementation ThreadSafeClass
-(instancetype)init{
	if (self = [super init]){
		
		self -> rLock = [NSRecursiveLock new];
	}
	return self;
}

-(void)safeMethod{
	[self -> rLock lock];//safeMethod獲取鎖
	
	[self safeMethod2];//調用safeMethod2方法
	
	[self -> rLock unlock];//safeMethod釋放了鎖。由於每一個鎖定操做都有一個相應的解鎖操做與之匹配,因此鎖如今被釋放,並能夠被其餘線程所獲取。
}

-(void)safeMethod2{
	[self -> rLock lock];//safeMethod2從已經獲取到的鎖再次獲取了鎖
	
	//線程安全的代碼
	
	[self -> rLock unlock];//safeMethod2釋放了鎖
}
  • NSCondition

    NSCondition能夠原子性地釋放鎖,從而使得其餘等待的線程能夠獲取鎖,而初始的線程繼續等待。

@implementation Producer
  //1生產者的初始化器須要用於協調配合的NSCondition對象和用於存放產品的collector
-(instancetype)initWithCondition:(NSCondition *) condition collector:(NSMutableArray *)collector{
	if (self = [super init]){
		self.condition = condition;
		self.collector = collector;
		self.shouldProduce = NO;
		self.item = nil;
	}
	return self;
}

-(void)produce{
	self.shouldProduce = YES;
	while (self.shouldProduce) {//2.生產者會在shouldProduce爲YES時進行生產。其餘線程須要將設置爲NO以中止生產者的生產
		[self.condition lock];
		if (self.collector.count > 0 ){
			[self.condition wait];//4.若是collector中有未消費的產品,則等待,這會阻塞當前線程的執行直到condition被通知(signal)爲止
		}
//		[self.collector addObject:[self nextItem]];//5.
		[self.condition signal];//6.通知其餘等待的線程。這裏是產品完成生產的標誌,並將產品將入到了collector中,可供消費
		[self.condition unlock];
	}
}

@implementation Consumer
-(instancetype)initWithCondition:(NSCondition *) condition collector:(NSMutableArray *)collector{//8.
	if (self = [super init]){
		self.condition = condition;
		self.collector = collector;
		self.shouldConsume = NO;
		self.item = nil;
	}
	return self;
}

-(void)consume{
	self.shouldConsume = YES;
	while (self.shouldConsume) {//9.shouldConsume爲YES,消費者進行消費。其餘線程能夠將其設置爲NO來中止消費者的消費
		[self.condition lock];
		if (self.collector.count == 0){
			[self.condition wait];//11若是collector沒有產品則等待
		}
	}
	id item = [self.collector objectAtIndex:0];
	NSLog(@"%@",item);
	//處理產品
	[self.collector removeObjectAtIndex:0];//12消費collector的中的下一個產品。確保已從collector中移除它。
	[self.condition signal];//13通知其餘等待的線程。這裏標識一個產品被消費並從collector中移除了
	[self.condition unlock];
}

@implementation Coordinator
-(void)start{
	NSMutableArray *prprline = [NSMutableArray array];
	NSCondition *condition = [NSCondition new];//Coordinator類爲生產者和消費者準備好了輸入數據
	Producer *p = [Producer new];
	p = [p initWithCondition:condition collector:prprline];
	Consumer *c = [Consumer new];
	c = [c initWithCondition:condition collector:prprline];//16
	NSThread *t = [NSThread new];
	//在不一樣的線程中開啓生產和消費任務
	[[t initWithTarget:self selector:@selector(startProducer) object:p]start];
	[[t initWithTarget:self selector:@selector(startCollector) object:c]start];
	
	p.shouldProduce = NO;
	c.shouldConsume = NO;//18.一旦完成,分別設置生產者和消費者中止生產和消費
	
	[condition broadcast];//19.由於生產者和消費者線程可能會等待,因此broadcast本質上會通知全部等待中的線程。不一樣的是,signal方法只會影響一個等待的線程。
	
}

4.5.4 將讀寫鎖應用於併發讀寫

避免併發寫入的最佳實踐: 若是有多個線程視圖讀取一個屬性,同步的代碼在同一時刻只容許單個線程進行訪問。所以,使用atomic屬性會拖慢應用的性能。這多是個嚴重的瓶頸,尤爲是當某個狀態須要在多個線程間共享,且須要被多個線程訪問時。cookie或登陸後的訪問令牌就是這樣的例子。它能夠週期性的變化,但會被全部訪問服務器的網絡請求所調用。 GCD屏障容許在並行分發隊列上建立一個同步的點。當遇到屏障時,GCD會延遲執行提交的代碼塊,直到隊列中全部在屏障以前提交的代碼塊都執行完畢。隨後,經過屏障提交的代碼塊會單獨地執行。

輸入圖片說明

代碼實現步驟: (1)建立一個並行隊列 (2)使用dispatch_sync執行全部的讀操做 (3)在相同的隊列上使用dispatch_barrier_sync執行全部的寫操做

@interface HPCache : NSObject
+(HPCache *)shareInstance;
-(id)objectForKey:(id)key;
-(void)setObject:(id)object forKey:(id)key;
@end

@interface HPCache()
@property(nonatomic,readonly) NSMutableDictionary *cacheObjects;
@property(nonatomic,readonly) dispatch_queue_t queue;
@end
@implementation HPCache

-(instancetype) init{
	if (self = [super init]){
		_cacheObjects = [NSMutableDictionary dictionary];
		_queue = dispatch_queue_create(kCacheQueueName, DISPATCH_QUEUE_CONCURRENT);//1.建立自定義的DISPATCH_QUEUE_CONCURRENT隊列
	}
	return self;
}

+(HPCache *)shareInstance{
	static HPCache *instance = nil;
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		instance = [[HPCache alloc]init];
	});
	return instance;
}

-(id)objectForKey:(id)key{
	__block id rv = nil;
	
	dispatch_sync(self.queue, ^{//2.將dispatch_sync用於不修改狀態的操做
		rv = [self.cacheObjects objectForKey:key];
	});
	
	return rv;
}

-(void)setObject:(id)object forKey:(id<NSCopying>)key{
	dispatch_barrier_sync(self.queue, ^{//3.將dispatch_barrier_sync用於可能修改狀態的操做
		[self.cacheObjects setObject:object forKey:key];
	});
}
@end

4.5.5 使用不可變實體

遵循的最佳實踐:

  • 使用不可變實體。
  • 經過更新子系統提供支持。
  • 容許觀察者接收有關數據變化的通知。
@interface HPUser : NSObject 
@property (nonatomic,copy) NSString *userId;
@property (nonatomic,copy) NSString *firstName;
@property (nonatomic,copy) NSString *lastName;
@property (nonatomic,copy) NSString *gender;
@property (nonatomic,copy) NSDate *dateOfBirth;
@property (nonatomic,strong) NSArray *albums;
//+(instancetype) userWithBlock:(void(^)(HPUserBuilder *)) block;
@end

@class HPPhoto;

@interface HPAlbum
@property (nonatomic , copy) NSString *albumId;
@property (nonatomic , strong) HPUser *owner;
@property (nonatomic , copy) NSString *name;
@property (nonatomic , copy) NSString *descripiton;
@property (nonatomic , copy) NSDate *creationTime;
@property (nonatomic , copy) HPPhoto *coverPhoto;
@end

@interface HPPhoto
@property (nonatomic , copy) NSString *photoId;
@property (nonatomic , strong) HPAlbum *album;
@property (nonatomic , strong) HPUser *user;
@property (nonatomic , copy) NSString *caption;
@property (nonatomic , strong) NSURL *url;
@property (nonatomic , assign) CGSize size;
@end

@interface HPUserBuilder : NSObject  //生成器
@property (nonatomic , copy) NSString *userId;
@property (nonatomic , copy) NSString *firstName;
@property (nonatomic , copy) NSString *lastName;
@property (nonatomic , copy) NSString *gender;
@property (nonatomic , copy) NSDate *dateOfBirth;
@property (nonatomic , copy) NSArray *albums;

-(HPUser *)build;
@end


@interface HPUser ()//模型的私有擴展--自定義的初始化設置
-(instancetype) initWithBuilder:(HPUserBuilder *)builder;
@end


@implementation HPUserBuilder
-(HPUser *)build{
	return [[HPUser alloc]initWithBuilder:self];
}
@end

@implementation HPUser
-(instancetype) initWithBuilder:(HPUserBuilder *)builder{//模型自定義初始化器的實現
	if (self = [super init]){
		self.userId = builder.userId;
		self.firstName = builder.firstName;
		self.lastName = builder.lastName;
		self.gender = builder.gender;
		self.dateOfBirth = builder.dateOfBirth;
		self.albums = [NSArray arrayWithArray:_albums];
	}
	return self;
}
+(instancetype) userWithBlock:(void(^)(HPUserBuilder *)) block{//6
	HPUserBuilder *builder = [HPUserBuilder new];
	block(builder);
	return [builder build];
}

//生成器建立對象的使用示例
-(HPUser *)createUser{
	HPUser *rv = [HPUser userWithBlock:^(HPUserBuilder *builder){
		builder.userId = @"id001";
		builder.firstName = @"Zou";
		builder.lastName = @"Jie";
		builder.gender = @"F";
		
		NSCalendar *cal = [NSCalendar currentCalendar];
		NSDateComponents *components = [[NSDateComponents alloc]init];
		[components setYear:2017];
		[components setMonth:10];
		[components setDay:1];
		builder.dateOfBirth = [cal dateFromComponents:components];
		
		builder.albums = [NSArray array];
	}];
  return rv;
}
@end

4.5.6 狀態觀察者與通知

響應式編程是基於異步數據流的編程方式。流是廉價且無處不在的,一切均可以是流:變量,用戶輸入,屬性,緩存,數據結構,等等。 ReactiveCocoa庫(https://github.com/ReactiveCocoa/ReactiveObjC)實現了Objective-c中進行響應式編程。它不只能夠實現對任意狀態的觀察,還提供了高級的分類擴展,以便同步更新UI元素或響應視圖的交互。 Cocoapods集成(http://www.jianshu.com/p/7c786eee1705)後,引入ReactiveCocoa頭文件ReactiveObjC.h。

@interface HPUserService ()
@property (nonatomic , strong) NSMutableDictionary *userCache;
@end
@implementation HPUserService
-(RACSignal *)signalForUserWithId:(NSString *)userid{//1
	@weakify(self);
	return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {//2.
		@strongify(self);
		HPUser *userFromCache = [self.userCache objectForKey:userid];
		if (userFromCache){
			[subscriber sendNext:userFromCache];
			[subscriber sendCompleted];
		}else{
			
		}
		return nil;
	}];
}

-(RACSignal *)signalForUpateUser:(HPUser *)user{//更新HPUser對象
//	@weakify(self);
	return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {//4.
		//更新服務器
//		@strongify(self);
		[NSNotificationCenter.defaultCenter postNotificationName:@"userUpdated" object:nil];
		return nil;
	}];
}

-(RACSignal *)signalForUserUpdates:(id)object{
	return [[NSNotificationCenter.defaultCenter rac_addObserverForName:@"userUpdateed" object:object]
			flattenMap:^__kindof RACSignal * _Nullable(NSNotification * _Nullable value) {
		return value.object;
	}];//rac_addObserverForName 訂閱userUpdated通知
}

4.5.7 異步優於同步

在實際場景中使用dispatch_sync

//場景A
	dispatch_sync(queue, ^{
		dispatch_sync(queue, ^{
			NSLog(@"nested sync call");
		})
	});

//場景B
-(void) methodA1{
	dispatch_sync(queue1, ^{
//		[objB methodB];
	});
}
-(void)methodA2{
	dispatch_sync(queue1, ^{
		NSLog(@"indirect nested dispatch_sync");
	});
}
-(void)methodB{
	//	[objA methodA2];
}

場景A演示了一個假設的場景,在這個場景中使用分發隊列調用了一個嵌套的dispatch_sync。這會致使死鎖。嵌套的dispatch_sync不能分發到隊列中,由於當前線程已經在隊列中且不會釋放鎖。 場景B演示了更類似的場景。類A有兩個使用了相同隊列的方法(methodA1和methodA2)。前一個方法對某個對象調用了methodB方法,後續會反調回來。最終結果仍是死鎖。 要想實現線程安全,不死鎖且易於維護的代碼,使用異步風格。使用Promise是最好的方式。ReactiveCocoa爲Objective-C引入了FRP風格。dispatch_async不受這一行爲影響。

PromiseKit: githubhttps://github.com/mxcl/PromiseKit

處理地獄回調:http://www.jianshu.com/p/f060cfd52f17, http://www.cocoachina.com/swift/20160719/17085.html

相關文章
相關標籤/搜索