我只是一個搬運工,僅僅爲了加深記憶,感謝做者分享,文章大部分來源:進擊的蝸牛君git
幾乎全部的操做系統都支持同時運行多個任務,一個任務一般就是一個程序,每一個程序就是一個進程。當一個程序運行時,內部可能包含了多個順序執行流,每一個順序執行流就是一個線程。程序員
當一個程序進入內存運行後,即變成一個進程。進程是處因而處於運行過程當中的程序,而且具備必定的獨立功能,進程是系統進行資源分配和調度的一個獨立單位。通常而言,進程有以下特徵:github
線程也被稱作輕量級進程,線程是進程的執行單元。就像進程在系統中同樣,線程在進程中也是獨立的,併發的執行流程。一個進程能夠擁有多個線程,一個線程必須有一個父進程,但再也不擁有系統資源,而是和父進程一塊兒共享父進程的所有資源。多線程因爲共享父進程的資源,因此編程更加方便,可是也須要當心線程不會影響到父進程中的其餘線程。線程是獨立運行的,它並不知道其餘線程的存在。線程執行是搶佔式的,也就是說,當前運行的線程在任什麼時候候均可能被掛起,以便林另一個線程能夠運行。編程
爲了提升資源利用率來提高系統總體效率,實際每每是將耗時操做放在後臺執行,避免阻塞主線程,在iOS中UI繪製和用戶響應都是主線程。安全
經常使用APIbash
- (void)viewDidLoad {
[super viewDidLoad];
//打印當前線程
NSLog(@"開始:%@ 優先級:%d", [NSThread currentThread], [NSThread currentThread].qualityOfService);
//1.建立NSTread對象,必須調用start方法開始,而且只能傳一個參數object
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"test"];
// NSThread *thread = [[NSThread alloc] initWithBlock:^{}];
thread.name = @"testThread";
thread.qualityOfService = NSQualityOfServiceUserInteractive;
[thread start];
//2.直接建立並啓動線程
// [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"test"];
// [NSThread detachNewThreadWithBlock:^{}];
//3.隱式直接建立
// [NSThread performSelectorInBackground:@selector(run:) withObject:nil];
// NSLog(@"結束:%@", [NSThread currentThread]);
}
- (void)run:(NSObject *)object {
//阻塞休眠
// [NSThread sleepForTimeInterval:5];
//停止當前線程
// [NSThread exit];
NSLog(@"子線程運行:%@ %@ 優先級:%d", [NSThread currentThread], object, [NSThread currentThread].qualityOfService);
}
複製代碼
線程被啓動後,並非直接進入執行狀態,也不是一直處於執行狀態,因爲線程併發,線程會反覆在運行、就緒間切換。建立一個線程後,處於新建狀態,系統爲其分配內存,初始化成員變量;調用-(void)start;
方法後,該線程處於就緒狀態,系統爲其建立方法調用棧和程序計數器,此時並無運行,什麼時候運行取決於系統調度。 多線程
每一個線程都有必定的優先級,優先級越高得到執行機會越多。目前經過qualityOfService
屬性來設置,原來的threadPriority
因爲語義不夠清晰,已經被廢棄了。併發
NSQualityOfServiceUserInteractive:最高優先級,主要用於提供交互UI的操做,好比處理點擊事件,繪製圖像到屏幕上
NSQualityOfServiceUserInitiated:次高優先級,主要用於執行須要當即返回的任務
NSQualityOfServiceDefault:默認優先級,當沒有設置優先級的時候,線程默認優先級
NSQualityOfServiceUtility:普通優先級,主要用於不須要當即返回的任務
NSQualityOfServiceBackground:後臺優先級,用於徹底不緊急的任務
複製代碼
使用NSThread進行多線程編程較複雜,須要本身控制多線程的同步、併發,還須要本身控制線程的終止銷燬,稍有不留神容易出現錯誤,對開發者要求較高,通常較少使用。app
iOS還提供了NSOperation與NSOperationQueue來實現多線程,是基於GCD更高一層的封裝,徹底面向對象。可是GCD更簡單易用、代碼可讀性也更高。異步
NSOperationQueue:負責管理系統提交的多個NSOperation,底層維護了一個線程池。不一樣於GCD中的調度隊列FIFO(先進先出)原則。NSOperationQueue對於添加到隊列中的操做,首先進入準備就緒的狀態(就緒狀態取決於操做之間的依賴關係),而後進入就緒狀態的操做的開始執行順序(非結束執行順序)由操做之間相對的優先級決定(優先級是操做對象自身的屬性)。
NSOperation: 表明一個多線程任務。
NSOperationQueue *queue;
//獲取執行當前NSOperation的NSOperationQueue隊列
// queue = [NSOperationQueue currentQueue];
//獲取主線程的NSOperationQueue隊列
// queue = [NSOperationQueue mainQueue];
//自定義隊列
queue = [[NSOperationQueue alloc] init];
//隊列名
queue.name = @"testOperationQueue";
//最大併發操做數(系統有限制,即便設置很大,也會自動調整)
queue.maxConcurrentOperationCount = 10;
//設置優先級
queue.qualityOfService = NSQualityOfServiceDefault;
//自定義NSOperation,若是SEL和Block爲空,系統不會加入到指定隊列
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation");
}];
//添加依賴關係,invocationOperation執行完後才執行blockOperation
[blockOperation addDependency:invocationOperation];
//添加到隊列中
// [queue addOperation:invocationOperation];
[queue addOperations:@[invocationOperation, blockOperation] waitUntilFinished:NO];
//直接添加代碼塊任務
[queue addOperationWithBlock:^{
}];
//打印全部的NSOperation
for(int i=0; i<queue.operationCount; i++) {
NSLog(@"隊列%@的第%d個NSOperation:%@", queue.name, i, queue.operations[i]);
}
//終止全部NSOperation
// [queue cancelAllOperations];
//執行完全部NSOperation才能解除阻塞當前線程
// [queue waitUntilAllOperationsAreFinished];
複製代碼
注:隊列的串行和並行決定了任務以何種方式執行,執行的異步和同步決定了是否須要開闢新線程處理任務。
/** 獲取隊列 */
//獲取指定優先級的全局併發隊列(flag填0便可,僅預留的參數,使用其餘值可能會返回null)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//建立自定義並行隊列
dispatch_queue_t queue1 = dispatch_queue_create("testQueue1", DISPATCH_QUEUE_CONCURRENT);
//獲取系統主線程關聯的串行隊列
dispatch_queue_t queue2 = dispatch_get_main_queue();
//建立自定義串行隊列
dispatch_queue_t queue3 = dispatch_queue_create("testQueue3", DISPATCH_QUEUE_SERIAL);
/** 提交任務 */
//異步提交代碼塊到併發隊列
dispatch_async(queue, ^{
});
//同步提交代碼塊到自定義併發隊列
dispatch_sync(queue1, ^{
});
//異步提交代碼塊到串行隊列,線程池將在指定時間執行代碼塊(實際是5秒後加入到隊列中,實際並不必定會立馬執行,通常精度要求下是沒問題的)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5*NSEC_PER_SEC)), queue2, ^{
});
//異步提交代碼到自定義串行隊列,同步函數,不管是在串行仍是並行隊列中執行,都要執行完才返回,因此要防止線程阻塞和死鎖,time表示當前是第幾回(若是提交給併發隊列,會啓動五個線程來執行)
dispatch_apply(5, queue3, ^(size_t time) {
});
//實際是個long類型變量,用於判斷該代碼塊是否被執行過
static dispatch_once_t onceToken;
//主線程執行一次代碼塊
dispatch_once(&onceToken, ^{
});
//等group執行完後,才能執行下一步
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
/** 組(用於須要等待多個任務所有執行完再進行下一步) */
dispatch_group_t group = dispatch_group_create();
//併發執行的代碼塊1
dispatch_group_async(group, queue, ^{
});
//併發執行的代碼塊2
dispatch_group_async(group, queue, ^{
});
//等待兩個代碼塊執行完彙總
dispatch_group_notify(group, queue, ^{
});
/** 柵欄(用於須要依次執行完多個線程組) */
//併發隊列異步執行代碼塊1,2
dispatch_async(queue, ^{
//代碼塊1
});
dispatch_async(queue, ^{
//代碼塊2
});
//1,2執行完後纔會執行3,4
dispatch_barrier_async(queue, ^{
});
//併發隊列異步執行代碼塊3,4
dispatch_async(queue, ^{
//代碼塊3
});
dispatch_async(queue, ^{
//代碼塊4
});
/** 信號量(用於控制線程的等待和執行) */
//建立信號量,value表示初始信號總量,支持多少個操做來執行
dispatch_semaphore_t t = dispatch_semaphore_create(1);
//發送一個信號,讓信號總量+1
dispatch_semaphore_signal(t);
//使信號總量-1,若是總量爲0,則會一直等待(阻塞所在線程),直到總量大於0則繼續執行
dispatch_semaphore_wait(t, DISPATCH_TIME_FOREVER);
/*1.能夠將異步執行變爲同步執行,如須要等待下載完後再直接返回數據(咱們也能夠經過block回調)*/
//總信號量設置爲0
dispatch_semaphore_t t1 = dispatch_semaphore_create(0);
//執行耗時代碼
void (^downloadTask)(void) = ^ {
//下載圖片
...
...
//完成後發送信號量
dispatch_semaphore_signal(t1);
};
downloadTask();
//一直等到信號量計數爲1才執行下一步,也就是等到圖片下載完後
dispatch_semaphore_wait(t1, DISPATCH_TIME_FOREVER);
/*2.保證線程安全*/
//設置信號量初始計數爲1,保證只能有一個操做能進來
dispatch_semaphore_t t2 = dispatch_semaphore_create(1);
//至關於加鎖,消耗使用計數,若是已經被一個線程使用,後續只能掛起等待信號量回復
dispatch_semaphore_wait(t2, DISPATCH_TIME_FOREVER);
//執行業務代碼
...
...
//解鎖
dispatch_semaphore_signal(t2);
/*3.模擬NSOperationQueue的最大併發操做數*/
//最大併發操做支持10
dispatch_semaphore_t t3 = dispatch_semaphore_create(10);
//剩餘操做同上,其實就是相似於將NSOperationQueue的maxConcurrentOperationCount設置爲10
複製代碼
在App程序進入後臺時,咱們應該儘可能釋放內存和保存用戶數據或者狀態信息。在默認狀況下,應該僅在5秒鐘處理這些工做,咱們能夠經過UIApplication
的beginBackgroundTaskWithExpirationHandler
方法來申請延長處理時間,最多有十分鐘。
- (void)applicationDidEnterBackground:(UIApplication *)application {
//聲明關閉後臺任務代碼塊
void (^endBackgroundTask)(UIBackgroundTaskIdentifier backgroudTask) = ^(UIBackgroundTaskIdentifier backgroudTask) {
[[UIApplication sharedApplication] endBackgroundTask:backgroudTask];
backgroudTask = UIBackgroundTaskInvalid;
};
//開啓後臺任務
__block UIBackgroundTaskIdentifier backgroudTask;
backgroudTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
//十分鐘內仍然沒有完成,系統處理終止句柄
endBackgroundTask(backgroudTask);
}];
//執行相關代碼
//結束後臺任務
endBackgroundTask(backgroudTask);
}
複製代碼
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
複製代碼
在主隊列中增長同步代碼塊,就會形成死鎖,因爲同步是須要當即順序執行的,上述代碼中,Block中的方法須要在viewDidLoad
結束後才能完成,可是viewDidLoad
想要結束又必須先結束Block中的方法,因此相互永久等待,形成了死鎖。
直接使用GCD的相關API通常是不會的,block結束後沒有循環引用的條件,YYKit的issues下有個有去的討論:dispatch_async的block裏面須要_weak self嗎?
線程安全主要是因爲系統的線程調度具備必定的隨機性形成的,因爲是多併發,多個線程同時對一份數據進行讀寫,就可能在讀取執行通常的時候另一個線程去寫入,致使數據異常。線程安全即保證線程同步
爲了解決這個問題,Objective-C的多線程支持引入同步,使@synchronized
修飾代碼塊,被修飾的代碼塊可簡稱爲同步代碼塊,語法格式以下
@synchronized (obj) {
//同步代碼塊
}
複製代碼
其中obj
就是同步監視器,當一個線程執行同步前,必須先得到同步監視器的鎖定,任什麼時候刻只能有一個線程得到鎖定,執行完成後,纔會釋放,若是此時有新的線程訪問,那麼新線程會進入休眠狀態。一般推薦使用可能被併發訪問的共享資源做爲同步監視器。
1. OSSpinLock(自旋鎖)
#import <libkern/OSatomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//嘗試加鎖(若是須要等待就不加鎖,直接返回false;若是不須要等待加鎖,返回true)
bool resule = OSSpinLockTry(&lock);
//加鎖
OSSpinLock(&lock);
//解鎖
OSSpinLockUnlock(&lock);
複製代碼
2. os_unfair_lock
#import <os/lock.h>
//初始化
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
互斥鎖
#import <pthread.h>
//初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_NORMAL);
//初始化
pthread_mutex_t mutex;
pthread_mutex_init (&mutex,&attr);
//嘗試加鎖
pthread_mutex_trylock (&mutex);
//加鎖
pthread_mutex_lock (&mutex);
//解鎖
pthread_mutex_unlock (&mutex);
//銷燬相關資源
pthread_mutexattr_unlock(&attr);
pthread_mutex_destroy(&mutex);
複製代碼
遞歸鎖
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(mutex, &attr);
// 銷燬屬性
pthread_mutexattr_destroy(&attr);
複製代碼
條件
// 初始化鎖
pthread_mutex_t mutex;
//NULL表明使用默認屬性
pthread_mutex_init(&mutex, NULL);
// 初始化條件
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
//等待條件(進入休眠,放開mutex鎖;被喚醒後,會再次對mutex加鎖)
pthread_cond_wait(&cond, &mutex);
//激活一個等待條件的線程
pthread_cond_signal(&cond);
//銷燬資源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
複製代碼
4. NSLock、NSRecursiveLock
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking>
{
- (BOOL)tryLock;
- (BOOl)lockBeforeDate:(NSDate *)limit;
}
@end
//初始化鎖
NSLock *lock = [[NSLock alloc] init];
複製代碼
5. NSCondition
@interface NSCondition : NSObject <NSLocking>
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
複製代碼
6. NSConditionLock
@interface NSConditionLock : NSObject <NSLocking> {
- (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;
}
@end
複製代碼
7. dispatch_semaphore
//信號量的初始值
int value = 1;
//初始化信號量
dispatch_semaphore semephore = dispatch_semaphore_creat(value);
//若是信號量的值<=0,當前線程就會進入休眠等待(直到信號量的值>0)
//若是信號量的值>0, 就減1,而後往下執行後面的代碼
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//讓信號量的值加1
dispatch_semaphore_signal(semaphore);
複製代碼
8. dispatch_queue
dispatch_queue_t queue = dispatch_queue_creat("lock_queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//任務
})
複製代碼