移動開發(IOS) – 多線程

移動開發(IOS) – 多線程

By docoder in 博客學習 on 2014/07/04

thread

1.概念 

1.1.系統中的每一個進程都有自己獨立的虛擬內存空間,而同一個進程中的多個線程則共用進程的內存空間。

1.2.每創建一個新的線程,都會消耗一定內存和CPU時間。

1.3.當多個線程對同一個資源出現爭奪的時候需要注意線程安全問題。

1.4.多線程的優勢:

1.4.1.充分發揮多核處理器優勢,將不同線程任務分配給不同的處理器,真正進入“並行運算”狀態。

1.4.2.將耗時、輪詢或者併發需求高等任務分配到其他線程執行,並由主線程負責統一更新界面會使得應用程序更加流暢,用戶體驗更好。

1.4.3.當硬件處理器的數量增加,程序會運行更快,而無需做任何調整。

1.5.多線程的難點:

1.5.1.共享資源的“爭奪。

1.5.2.多線程是爲了同步完成多項任務,不是爲了提高運行效率,而是爲了通過提高資源使用效率來提高系統的整體性能。

1.6.只有主線程有直接修改UI的能力。

1.7.內存管理對於多線程非常重要。

1.7.1.Objective-C 可以憑藉 @autoreleasepool 使用內存資源,並需要時回收資源。

1.7.2.每個線程都需要有 @autoreleasepool ,否則可能會出現內存泄漏。

2.iOS的三種多線程技術

2.1.NSThread 每個 NSThread 對象對應一個線程,量級較輕。

2.1.1.優點:NSThread 比其他兩個輕量級,使用簡單。

2.1.2.缺點:需要自己管理線程的生命週期、線程同步、加鎖、睡眠以及喚醒等。線程同步對數據的加鎖會有一定的系統開銷。

2.2.NSOperation/NSOperationQueue 面向對象的線程技術。

2.2.1.不需要關心線程管理,數據同步的事情,可以把精力放在自己需要執行的操作上。

2.2.2.NSOperation是面向對象的。

2.3.GCD(Grand Central Dispatch) 是基於 C 語言的框架,可以充分利用多核,是蘋果推薦使用的多線程技術。

2.3.1.Grand Central Dispatch 是由蘋果開發的一個多核編程的解決方案。iOS4.0+才能使用,是替代 NSThread, NSOperation的高效和強大的技術。

2.3.2.GCD 是基於 C 語言的。

以上這三種編程方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡單,也是Apple最推薦使用的。

3.NSObject的多線程方法

3.1.通常,由於線程管理相對比較繁瑣,而很多耗時的任務又無法知道其準確的完成時間,因此可以使用 performSelectorInBackground 方法直接新建一個後臺線程,並將選擇器指定的任務在後臺線程執行,而無需關心具體的 NSThread 對象:

1
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

3.1.1.performSelectorInBackground 方法本身是在主線程中執行的,而選擇器指定的方法是在後臺線程中進行的。

3.1.2.使用 performSelectorInBackground 方法調用的任務可以更新 UI 界面。

3.2.如果要更新UI界面,可以在後臺線程中調用 performSelectorOnMainThread 方法:

1
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

儘管使用 performSelectorInBackground 方法調用的任務可以更新UI界面,但是在實際開發中,涉及到 UI 界面的更新操作,還是要使用 performSelectorOnMainThread 方法,以避免不必要的麻煩。

3.3.獲取線程信息:

1
[NSThread currentThread];

3.4.線程休眠(僅適用於開發調試時使用):

1
[NSThread sleepForTimeInterval:1.0f];

3.5.特點:使用簡單,量級輕;不能控制線程的執行順序。

4.資源爭奪

4.1.僅使用單例模式無法解決資源爭奪問題。

4.2.使用同步鎖 @synchronized 可以保證多個線程不會使用同一代碼塊,而且比 NSLock 具有更好的性能。

4.3.爲了保證屬性安全,被爭奪資源的屬性應該設置爲原子屬性 atomic。

5.NSThread

5.1.創建線程方法:

1
2
3
4
5
6
7
8
/*
 * 參數說明:
 * selector:線程執行的方法,只能有一個參數,不能有返回值
 * target:selector消息發送的對象
 * argument:傳輸給target的唯一參數,也可以是nil
 */
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;

5.1.1.detachNewThreadSelector 方法會直接啓動線程方法。

5.1.2.initWithTarget 需要調用 start 方法才能夠啓動線程方法。

1
2
3
4
5
6
//類方法
[NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:) toTarget:self withObject:@"thread-1"];
//init方法
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadSaleTicketWithName:) object:@"thread-2"];
// 啓動線程
[thread start];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)threadSaleTicketWithName:(NSString *)name
{
     // 使用 NSThread 時,線程調用的方法千萬要使用 @autoreleasepool
     @autoreleasepool {
        while (YES) {
            @synchronized(self) {
                if ([Ticket sharedTicket].tickets > 0) {
                    [Ticket sharedTicket].tickets--;
                    NSString *str = [NSString stringWithFormat:@"剩餘票數 %d 線程名稱 %@", [Ticket sharedTicket].tickets, name];
                    // 更新UI
                    [self performSelectorOnMainThread:@selector(appendContent:) withObject:str waitUntilDone:YES];
                } else {
                    break;
                }
            }
            // 模擬休息
            if ([name isEqualToString:@"thread-1"]) {
                [NSThread sleepForTimeInterval:1.0f];
            } else {
                [NSThread sleepForTimeInterval:0.1f];
            }
        }
    }
}

6.NSOperation  &  NSOperationQueue

6.1.NSOperation 的兩個子類:NSInvocationOperation, NSBlockOperation。

6.2.工作原理:

6.2.1.用 NSOperation 封裝要執行的操作。

6.2.2.將創建好的 NSOperation 對象放 NSOperationQueue 中。

6.2.3.啓動 OperationQueue 開始新的線程執行隊列中的操作。

6.3.注意事項:

6.3.1.使用 NSBlockOperation 更加簡單直接。

6.3.2.定義完操作後,將添加到操作隊列中,即可啓動異步操作,否則操作任務仍然在主線程中執行。

6.3.3.使用多線程時通常需要控制線程的併發數,因爲線程會消耗系統資源,同時運行的線程過多,系統會變慢。

6.3.4.使用以下方法可以控制併發的線程數量:

1
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

6.3.5.使用 addDependency 可以建立操作之間的依賴關係,設定操作的執行順序。

6.4.更新界面時使用 [[NSOperationQueue mainQueue]addOperationWithBlock: 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//隊列可以設置同時併發線程的數量
[self.queue setMaxConcurrentOperationCount:2];
 
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載 %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"美化 %@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"更新 %@", [NSThread currentThread]);
    //在主線程更新 UI
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
         NSLog(@"更新 UI %@", [NSThread currentThread]);
    }];
}];
// Dependency依賴
// 依賴關係可以多重依賴
// 不要建立循環依賴
[op2 addDependency:op1];
[op3 addDependency:op2];
 
[self.queue addOperation:op3];
[self.queue addOperation:op1];
[self.queue addOperation:op2];

7.GCD

7.1.GCD 是基於 C 語言的框架。

7.2.工作原理:

7.2.1.讓程序平行排隊的特定任務,根據可用的處理資源,安排它們在任何可用的處理器上執行任務。

7.2.2.要執行的任務可以是一個函數或者一個 block 。

7.2.3.底層是通過線程實現的,不過程序員可以不必關注實現的細節。

7.2.4.GCD 中的 FIFO 隊列稱爲 dispatch queue,可以保證先進來的任務先得到執行。

7.2.5.dispatch_notify 可以實現監聽一組任務是否完成,完成後得到通知。

7.3.GCD 隊列:

7.3.1.全局隊列:所有添加到主隊列中的任務都是併發執行的。(可能會開啓多條線程)

1
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

7.3.2.串行隊列:所有添加到串行隊列中的任務都是順序執行的。(只可能會開啓一條線程)

1
2
3
//創建串行隊列,串行隊列不能夠獲取。
//隊列名稱可以隨意,不過不要使用@
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);

 

7.3.3.主隊列:所有添加到主隊列中的任務都是在主線程中執行的。

1
dispatch_get_main_queue();

7.4.GCD任務的執行方式

7.4.1.異步操作:dispatch_async 在其他線程執行任務,會開啓新的線程,異步方法無法確定任務的執行順序。

7.4.2.同步操作:dispatch_sync 在當前在當前線程執行任務,不開啓新的線程。同步操作與隊列無關,同步方法會依次執行,能夠決定任務的執行順序,更新界面 UI 時,最好使用同步方法。

7.5.GCD 的優點:

7.5.1.充分利用多核。

7.5.2.所有的多線程代碼集中在一起,便於維護。

7.5.3.GCD 中無需使用 @autoreleasepool。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 獲取全局隊列
//優先級:DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (UIImageView *imageView in self.imageViewSet) {
    // 2. 在全局隊列上異步調用方法,加載並更新圖像
    dispatch_async(queue, ^{
        NSLog(@"GCD- %@", [NSThread currentThread]);
        NSInteger num = arc4random_uniform(17) + 1;
        NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png", num];
        // 通常此處的image是從網絡上獲取的
        UIImage *image = [UIImage imageNamed:imageName];
        // 3. 在在主線程隊列中,調用同步步方法設置UI
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"更新圖片- %@", [NSThread currentThread]);
            [imageView setImage:image];
        });
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 創建全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// GCD中可以將一組相關聯的操作,定義到一個羣組中
// 定義到羣組中之後,當所有線程完成時,可以獲得通知
// 1) 定義羣組
dispatch_group_t group = dispatch_group_create();
// 2) 定義羣組的異步任務
dispatch_group_async(group, queue, ^{
    [self gcdSaleTicketWithName:@"gcd-1"];
});
dispatch_group_async(group, queue, ^{
    [self gcdSaleTicketWithName:@"gcd-2"];
});
dispatch_group_async(group, queue, ^{
    [self gcdSaleTicketWithName:@"gcd-3"];
});
// 3) 羣組任務完成通知
dispatch_group_notify(group, queue, ^{
    NSLog(@"賣完了");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)gcdSaleTicketWithName:(NSString *)name
{
    while (YES) {
        // 同步鎖synchronized要鎖的範圍,對被搶奪資源修改/讀取的代碼部分
        @synchronized(self) {
            if ([Ticket sharedTicket].tickets > 0) {
                [Ticket sharedTicket].tickets--;
                // 提示內容
                NSString *str = [NSString stringWithFormat:@"剩餘票數 %d, 線程名稱 %@", [Ticket sharedTicket].tickets, name];
                // 更新界面
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self appendContent:str];
                });
            } else {
                break;
            }
        }
        // 模擬線程休眠
        if ([name isEqualToString:@"gcd-1"]) {
            [NSThread sleepForTimeInterval:1.0f];
        } else {
            [NSThread sleepForTimeInterval:0.2f];
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章