一, 程序良姆、進程和線程
程序:由源代碼生成的可執(zhí)行應用
進程: 一個正在運行的程序可以看做是一個進程
線程:程序中獨立運行的代碼段。
一個進程是由一個或多個線程組成。進程只負責資源的調(diào)度和分配堕汞, 線程才是程序真正的執(zhí)行單元, 負責代碼的執(zhí)行晃琳。
線程和進程的區(qū)別:
1讯检, 地址空間:線程是進程內(nèi)的一個執(zhí)行單元;進程至少有一個線程卫旱, 他們共享進程的地址空間人灼; 而地址有自己的獨立的地址空間;
2顾翼, 資源擁有:進程是資源分配和擁有的單位投放, 同一個進程內(nèi)的線程共享進程的資源;
3适贸, 線程是處理器調(diào)度的基本單位灸芳, 而進程不是涝桅;
4, 二者都可以并發(fā)執(zhí)行
**************iOS中關于UI的添加和刷新必須在主線程中操作
二烙样, 多線程的實現(xiàn)種類
iOS中多線程的實現(xiàn)種類有:NSThread冯遂,NSObject,NSOperationQueue谒获,GCD
1, NSThread是一個輕量級的多線程蛤肌, 有以下兩種創(chuàng)建方式:
(1)初始化開辟子線程
- (void)allocThread {
//初始化開辟子線程
//object 就是子線程方法的參數(shù)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:@1000];
//*********初始化開辟線程的話需要手動開啟
thread.name = @"zhengzhou";
// _thread.isCancelled 是否被取消
// _thread.isExecuting 是否正在執(zhí)行
// _thread.isMainThread 是否是主線程
[thread start];
}
- (void)threadAction:(NSNumber *)number {
//使用NSThread開辟的子線程, 方法內(nèi)部默認沒有添加自動釋放池, 為了防止內(nèi)存泄露, 我們需要手動添加自動釋放池
@autoreleasepool {
for (int i = 0; i < [number integerValue]; i++) {
NSLog(@"%@", [NSThread currentThread]);
if (i == 100) {
// cancle 并不能取消正在執(zhí)行的線程,只能取消還沒有開始執(zhí)行的線程
[[NSThread currentThread] cancel];
// 立即停止線程
[NSThread exit];
}
NSLog(@"%d", i);
}
}
}
(2)便利構造器開辟子線程
//不需要手動開啟
[NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:@1000];
我們一般不用NSThread來開辟子線程
2, NSObject
//NSObject
- (void)objectAction {
//開辟子線程
[self performSelectorInBackground:@selector(reuestImageData:) withObject:@100];
}
//子線程方法
- (void)reuestImageData:(NSString *)string {
//手動添加自動釋放池
@autoreleasepool {
//回到主線程刷新UI
//最后一個參數(shù)為YES時批狱, 是等主線程的方法執(zhí)行完之后在執(zhí)行線程后面的方法
//最后一個參數(shù)為NO時裸准, 是主線程和子線程中的方法同時執(zhí)行
[self performSelectorOnMainThread:@selector(updateUI:) withObject:nil waitUntilDone:NO];
for (int i = 0; i < 10; i++) {
NSLog(@"%d********%@", i, [NSThread currentThread]);
}
}
}
//刷新UI
- (void)updateUI:(NSData *)data {
for (int i = 0; i < 10; i++) {
NSLog(@"%d+++++++++%@", i, [NSThread currentThread]);
}
}
3, NSOperationQueue
NSOperation在MVC中屬于M赔硫,是用來封裝單個任務相關的代碼和數(shù)據(jù)的抽象類炒俱,不能夠直接使用這個類,而是使用子類(NSInvocationOperation或NSBlockOperation)來執(zhí)行實際任務卦停。
只是一個操作向胡,本身無主線程子線程之分,可在任意線程中使用惊完。通常與NSOperationQueue結(jié)合使用僵芹,只是封裝了一定的代碼段和數(shù)據(jù)去實現(xiàn)一個功能。
- (void)invocationOperationAction {
//NSOperation的子類, 創(chuàng)建的操作和線程沒有關系, 在主線程中創(chuàng)建的操作, 方法在主線程中進行, 在子線程中創(chuàng)建的操作在子線程中進行
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethod:) object:@"hello"];
//需要手動開啟任務
[operation start];
//取消任務
// [operation cancel];
//是否正在執(zhí)行
// [operation isExecuting];
//是否完成任務
// [operation isFinished];
}
- (void)invocationOperationMethod:(NSString *)string {
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", string);
}
// NSBlockOperation
- (void)blockOperation {
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//執(zhí)行的方法
NSLog(@"%@", [NSThread currentThread]);
}];
//需要手動開啟
[blockOperation start];
}
NSOperationQueue是操作隊列, 用來管理一組Operation對象的執(zhí)行, 會根據(jù)需要自動為Operation開辟合適數(shù)量的線程, 已完成任務的執(zhí)行
其中NSOperation可以調(diào)節(jié)它在隊列中的優(yōu)先級(使用addDependency: 設置依賴關系)
當最大并發(fā)數(shù)設置為1的時候, 能實現(xiàn)線程同步(串行執(zhí)行)
- (void)operationQueue {
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 1000000; i++) {
NSLog(@"1 ------ %d", i);
}
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 1000000; i++) {
NSLog(@"2 ------ %d", i);
}
}];
//創(chuàng)建一個新的隊列
//新創(chuàng)建的隊列, 里面的任務是并發(fā)執(zhí)行的, 而且是在子線程中執(zhí)行的
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//設置任務最大并發(fā)數(shù), 如果為1, 隊列里邊的任務相當于串行
// queue.maxConcurrentOperationCount = 1;
//添加依賴關系, 必須在任務添加到隊列之前設置
[blockOperation1 addDependency:blockOperation2];
[queue addOperation:blockOperation1];
[queue addOperation:blockOperation2];
}
4小槐,GCD(Grand Central Dispatch)
是Apple開發(fā)的一種多核編程技術. 主要用于優(yōu)化應用程序一支持多核處理器以及其他對稱多處理系統(tǒng)
GCD提供函數(shù)實現(xiàn)多線程開發(fā), 性能更高, 功能也更加強大
首次發(fā)布在Mac OS X 10.6, iOS4以上可用
核心概念:
任務: 具有一定功能的代碼段, 一般是一個block或者函數(shù)拇派, 與NSOperation一樣,并無主線程與子線程之分凿跳,主要是看它加載在什么queue隊列中
分發(fā)隊列: GCD隊列的方式進行工作
GCD會根據(jù)分發(fā)隊列的類型, 創(chuàng)建合適數(shù)量的線程執(zhí)行隊列中的任務
dispatch queue有串行隊列和并發(fā)隊列兩種:
(1)串行隊列(SerialQueue):一次只執(zhí)行一個任務. 一般用于同步訪問特定的資源和數(shù)據(jù). 當創(chuàng)建多個SerialQueue時, 雖然他們是同步執(zhí)行的, 但他們之間是并發(fā)執(zhí)行的.SerialQueue能實現(xiàn)線程同步
//主隊列
- (void)mainQueueAction {
//獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
//主隊列中添加任務
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"-------------------%@", [NSThread mainThread]);
}
});
//主隊列中添加任務
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"++++++++++++++++++++%@", [NSThread mainThread]);
}
});
}
//自定義串行隊列
- (void)customSerialQueue {
//創(chuàng)建一個串行隊列
//參數(shù):(1)隊列的名字, (2)隊列類型, 是串行還是并行
//串行里面的任務是在同一個線程中執(zhí)行的
dispatch_queue_t serialQueue = dispatch_queue_create("com.lanou3g.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"---------------------%@", [NSThread currentThread]);
}
});
dispatch_async(serialQueue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"++++++++++++++++++++%@", [NSThread currentThread]);
}
});
}
(2)并發(fā)隊列(Concurrent):可以并發(fā)地執(zhí)行多個任務
//全局隊列
- (void)globalQueue {
//參數(shù): (1)參數(shù)優(yōu)先級, (2)預留參數(shù)
//隊列里面的任務并發(fā)執(zhí)行
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//添加任務
dispatch_async(globalQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"----------%@", [NSThread currentThread]);
}
});
dispatch_async(globalQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"+++++++++++%@", [NSThread currentThread]);
}
});
}
//自定義并發(fā)隊列
- (void)customConcurrentQueue {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lanou3g.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"------------------%@", [NSThread currentThread]);
}
});
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"+++++++++++++++++++%@", [NSThread currentThread]);
}
});
}
(3)GCD功能
GCD功能
dispatch_async() //往隊列中添加任務, 任務會排隊執(zhí)行
dispatch_after() //往隊列中添加任務, 任務不但會排隊, 還會在延遲的時間點執(zhí)行
//延遲執(zhí)行任務
- (void)delayPerformQueue {
dispatch_queue_t queue = dispatch_queue_create("com.lanou3g.delayPerformQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%@", [NSThread currentThread]);
}
});
//延遲執(zhí)行任務
//queue隊列里面的任務執(zhí)行完成之后, 延遲5秒執(zhí)行該函數(shù)里面的任務
//不能在主隊列中執(zhí)行該任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"延遲執(zhí)行的任務");
});
}
dispatch_apply() //往隊列中添加任務, 任務會重復執(zhí)行n次
//重復執(zhí)行任務
- (void)repeatPerformTask {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//注意: queue 參數(shù)不能是主隊列(死鎖)
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%ld, %@", index, [NSThread currentThread]);
});
}
dispatch_group_async()//將任務添加到隊列中, 并添加分組標記
dispatch_group_notify() //將任務添加到隊列中, 當某個分組的所有任務執(zhí)行完之后, 此任務才會執(zhí)行
//分組標記功能
- (void)groupTask {
dispatch_queue_t queue = dispatch_queue_create("waige", DISPATCH_QUEUE_CONCURRENT);
//創(chuàng)建分組
dispatch_group_t group = dispatch_group_create();
//往隊列里面添加任務1
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"------------%@", [NSThread currentThread]);
}
});
//往隊列里面添加任務2
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"+++++++++++++++%@", [NSThread currentThread]);
}
});
//給隊列添加任務, 并添加分組標記
dispatch_group_notify(group, queue, ^{
NSLog(@"queue里面的所有任務都已經(jīng)執(zhí)行完畢");
});
}
dispatch_barrier_async() //將任務添加到隊列中, 此任務執(zhí)行的時候, 其他任務停止執(zhí)行
//添加障礙
- (void)barrierFunction {
//創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create("waige", DISPATCH_QUEUE_CONCURRENT);
//添加任務
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"寫入數(shù)據(jù)");
}
});
//設置屏障, 隊列里面前面的任務執(zhí)行完之后才執(zhí)行屏障函數(shù)里面的任務
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 20; i++) {
NSLog(@"不要打擾我, 哥在寫數(shù)據(jù)呢");
}
});
//屏障函數(shù)的任務完成之后才會執(zhí)行下面的任務
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"寫完了, 大家可以讀數(shù)據(jù)了");
}
});
}
dispatch_once() //任務添加到隊列中, 但任務在程序運行過程中, 只執(zhí)行一次
可用于單例的創(chuàng)建
該函數(shù)接收一個dispatch_once用于檢查該代碼塊是否已經(jīng)被調(diào)度的謂詞(是一個長整型, 實際上作為BOOL使用), 它在接收一個希望在應用的生命周期內(nèi)僅被調(diào)度一次的代碼塊. 不僅意味著代碼僅會被運行一次, 而且還是線程安全的, 這就意味著你不需要使用諸如@synchronized之類的來防止使用多個線程或者隊列時不同步的問題
+ (instancetype)sharedSingleTon {
static SingleTon *singleTon = nil;
//只能同時允許一個線程訪問
@synchronized(self) {
if (singleTon == nil) {
singleTon = [[SingleTon alloc] init];
}
}
return singleTon;
}
//GCD創(chuàng)建單例
+ (instancetype)defaultSingleTon {
static SingleTon *single = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//這里的代碼只執(zhí)行一次
single = [[SingleTon alloc] init];
});
return single;
}
dispatch_sync() //將任務添加到隊列中, block不執(zhí)行完, 下面代碼不會執(zhí)行(同步),會阻塞當前線程 ----------在當前線程中執(zhí)行
dispatch_async_f() //將任務添加到隊列中, 任務是函數(shù)非block(異步)---------重新創(chuàng)建線程執(zhí)行任務
//同步
- (void)useOfSync {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//dispatch_sync會阻塞當前線程
dispatch_sync(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%@", [NSThread currentThread]);
}
});
NSLog(@"這是一條分割線");
dispatch_sync(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"=====%@", [NSThread currentThread]);
}
});
NSLog(@"這又是一條分割線");
}
三件豌,線程間的通信
子線程回到主線程的方法
//bool參數(shù) 如果是YES, 子線程會等待主線程的任務完成以后才會繼續(xù)執(zhí)行, 如果為NO, 主線程的任務沒有結(jié)束的時候就會執(zhí)行子線程的任務
[self performSelectorOnMainThread:@selector(updataUI:) withObject:data waitUntilDone:NO];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageWithData:data];
});
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = [UIImage imageWithData:data];
}];
四,線程互斥
對線程并行編程中, 線程間的同步和互斥是一個很有技巧的也很容易出錯的地方控嗜。
多線程操作同一個資源(即某個對象), 需要保證線程在對資源的狀態(tài)(即對象的成員變量)進行一些非原子性操作后, 狀態(tài)仍然正確茧彤。
情景: 三個售票窗口同時售票, 可只剩最后一張票, 如何解決
解決方案
1,@synchronized 自動對參數(shù)對象加鎖, 保證臨街區(qū)內(nèi)的代碼線程安全
2疆栏,NSLock
//創(chuàng)建一個并行隊列
dispatch_queue_t queue = dispatch_queue_create("zz", DISPATCH_QUEUE_CONCURRENT);
//創(chuàng)建鎖對象
NSLock *lock = [[NSLock alloc] init];
//開辟10個子線程, 進行售票
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
//加鎖
[lock lock];
//售票任務
while (self.ticketsCount > 0) {
//線程休息0.1秒
[NSThread sleepForTimeInterval:0.1];
//售出一張
self.ticketsCount--;
NSLog(@"剩余票數(shù):%ld, 當前線程:%@", self.ticketsCount, [NSThread currentThread]);
}
//解鎖
[lock unlock];
});
}
3曾掂,NSConditionLock條件鎖 可以設置條件
//創(chuàng)建一個并行隊列
dispatch_queue_t queue = dispatch_queue_create("zz", DISPATCH_QUEUE_CONCURRENT);
//條件鎖
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:2];;
//開辟10個子線程, 進行售票
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
//條件鎖
[lock lockWhenCondition:2];
//售票任務
while (self.ticketsCount > 0) {
//線程休息0.1秒
[NSThread sleepForTimeInterval:0.1];
//售出一張
self.ticketsCount--;
NSLog(@"剩余票數(shù):%ld, 當前線程:%@", self.ticketsCount, [NSThread currentThread]);
}
//解鎖
[lock unlockWithCondition:2];
});
}
4,NSRecursiveLock 遞歸鎖 多次調(diào)用不會阻塞以獲取該鎖的線程
//創(chuàng)建一個并行隊列
dispatch_queue_t queue = dispatch_queue_create("zz", DISPATCH_QUEUE_CONCURRENT);
//遞歸鎖
recursiveLock = [[NSRecursiveLock alloc] init];
//開辟10個子線程, 進行售票
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
//遞歸鎖
[recursiveLock lock];
//售票任務
while (self.ticketsCount > 0) {
//線程休息0.1秒
[NSThread sleepForTimeInterval:0.1];
//售出一張
self.ticketsCount--;
NSLog(@"剩余票數(shù):%ld, 當前線程:%@", self.ticketsCount, [NSThread currentThread]);
}
//解鎖
[recursiveLock unlock];
});
}
五壁顶,總結(jié)
NSThread, NSOperationQueue, NSObject, GCD 都能實現(xiàn)多線程
1珠洗,GCD是蘋果提供的性能更高的方式
使用多線程開發(fā)的優(yōu)點: 資源利用率更好, 程序設計在某些情況下更簡單, 程序響應更快
缺點: 多線程金環(huán)提升了性能, 但是存在一些訪問限制, 比如線程同步, 線程互斥等; 多線程在使用的時候, 最終是要回到主線程刷新UI的, 如果開辟過多的多線程, 會造成CPU的消耗
2,NSOperationQueue和GCD 的區(qū)別
(1)GCD是基于C語言構成的API, 而NSoperationQueue是NSObject對象,在GCD 中在隊列中執(zhí)行的是由block構成的任務, 而NSOperationQueue有兩種方式可供選擇:NSInvocationOperation和NSBlockOperation
(2)在NSOperationQueue中可以隨時取消正在等待執(zhí)行隊列, 而GCD無法停止已經(jīng)加入queue隊列的block塊(其實是有的, 只不過代碼比較復雜)
(3)在NSOperation中我們可以為任務設定依賴關系,
(4)我們可以講KVO應用在NSOperation對象中, 可以監(jiān)聽一個operation是否完成或取消, 這樣可以更有效的掌控我們的后臺任務
(5)在NSOperation中我們可以設置NSOperation的priority的優(yōu)先級, 能夠使同一個并行的序列中的任務區(qū)分先后的執(zhí)行, 而在GCD中, 我們只能區(qū)分不同任務隊列的優(yōu)先級, 如果要區(qū)分block任務的優(yōu)先級, 也需要大量的代碼
(6)我們能夠?qū)SOperation進行繼承, 在這智商添加成員變量與成員方法, 提高整個代碼的復用度, 這筆簡單地將block任務排入執(zhí)行隊列更有自由度, 能夠在其之上添加更多自定制的功能
3若专,OperationQueue提供了更多在編寫多線程程序時需要的功能, 并隱藏了許多線程調(diào)度, 線程取消與線程優(yōu)先級的復雜代碼, 為我們提供簡單API入口. 從編程原則來說, 一般我們需要盡可能的使用最高等級, 封裝完美的API, 在必須時才使用底層的API.
GCD更簡潔, 性能更好,而NSOperationQueue為我們提供了更多的選擇