
多線程相關的話題是面試過程中必不可少的話題。有些面試官可能是要你自己談談對多線程的認識,而有些則是出道題給你,讓你直接手寫或者讓你口述實現(xiàn)。
談談對多線程的認識
在OC中實現(xiàn)多線程的方法有3種(NSThread、GCD、NSOperation),
NSThread是蘋果官方提供的,可以直接操作線程對象。不過需要程序員自己管理線程的生命周期(主要是創(chuàng)建),所以偶爾用用。比如 [NSThread currentThread],它可以獲取當前線程信息,用于調試十分方便。而GCD和NSOperation都是系統(tǒng)自動管理線程周期。
GCD是基于C底層的API,是一種更輕量級的,會自動合理地利用更多的CPU內核(比如雙核、四核)。以FIFO(先進先出,后進后出)的順序執(zhí)行任務。GCD中的核心概念:任務 和隊列。任務:即操作,你想要干什么,說白了就是一段代碼,在GCD中就是一個 Block,所以添加任務十分方便。任務有兩種執(zhí)行方式: 同步執(zhí)行和異步執(zhí)行,他們之間的區(qū)別是是否會創(chuàng)建新的線程。有串行、并發(fā)、主隊列、全局隊列、組隊列。其中隊列任務組合的方式總共有7種,其中使用頻率最高的是:并發(fā)隊列+異步執(zhí)行(多個任務同時執(zhí)行并發(fā)執(zhí)行,會開啟多條線程)。關于GCD的一些其他具體組合使用方式,可查閱筆者之前的文章。點我查看
NSOperation是基于GCD更高一層的封裝,相對于GCD更加強大??梢越ooperation之間添加依賴關系、取消一個正在執(zhí)行的operation、暫停和恢復operationQueue等。關于NSOperation的其他一些知識點,可查閱筆者之前的文章。點我查看
多線程的面試題目
- (1)A,B,C三個線程,要求執(zhí)行完A,B后才能執(zhí)行C,怎么做?
實現(xiàn)思路一: 添加依賴關系,A、B都依賴于C
//1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.創(chuàng)建操作
NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A----%@",[NSThread currentThread]);
}];
NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B----%@",[NSThread currentThread]);
}];
NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C----%@",[NSThread currentThread]);
}];
//3.添加依賴
[operationC addDependency:operationA]; // 讓C 依賴于 A,則先執(zhí)行A,再執(zhí)行C
[operationC addDependency:operationB]; // 讓C 依賴于 B,則先執(zhí)行B,再執(zhí)行C
//4.添加操作到隊列中
[queue addOperation:operationA];
[queue addOperation:operationB];
[queue addOperation:operationC];

實現(xiàn)思路二:dispatch_group_notify
dispatch_queue_t queue = dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"A----%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"B----%@",[NSThread currentThread]);
});
//等前面的任務執(zhí)行完畢后 會自動執(zhí)行這個任務
dispatch_group_notify(group, queue, ^{
NSLog(@"C----%@",[NSThread currentThread]);
});

實現(xiàn)思路三:dispatch_barrier_(a)sync
dispatch_queue_t queue = dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"A----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"B----%@",[NSThread currentThread]);
});
// dispatch_barrier_async: 在它前面的任務執(zhí)行結束后它才執(zhí)行,在它后面的任務等它執(zhí)行完成后才會執(zhí)行
dispatch_barrier_async(queue, ^{
NSLog(@"C----%@",[NSThread currentThread]);
});

- (2)結合使用AFNetworking多次請求,實現(xiàn)線程同步以及依賴。
在使用AFNetworking之前,我們首先看一段代碼
需求:吃飯 睡覺 打豆豆 -> 任務完成
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
NSLog(@"吃飯");
});
dispatch_group_async(group, queue, ^{
NSLog(@"睡覺");
});
dispatch_group_async(group, queue, ^{
NSLog(@"打豆豆");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務完成");
});

那么使用AFNetworking之后呢,會觸發(fā)怎么的結果呢?我們一起拭目以待。
- (void)requestFormData{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
[self requestWithType:@"吃飯"];
});
dispatch_group_async(group, queue, ^{
[self requestWithType:@"睡覺"];
});
dispatch_group_async(group, queue, ^{
[self requestWithType:@"打豆豆"];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務完成");
});
}
- (void)requestWithType:(NSString *)type{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:@"http://www.mocky.io/v2/5b6685533200006a00ee11b1" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@",type);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {}];
}

綜上打印,你心里可能會產生一個疑惑,只是換成了AFNetworking,打印結果卻發(fā)生了變化,用過AFNetworking的伙伴們應該都知道在進行網(wǎng)絡請求的時候內部是又開了線程的,那么這時候我們應該怎么實現(xiàn)吃飯睡覺打豆豆這個需求呢,請接著繼續(xù)往下看。
方式一:使用信號量進行破解
- (void)requestWithType:(NSString *)type{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:@"http://www.mocky.io/v2/5b6685533200006a00ee11b1" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
long flag = dispatch_semaphore_signal(semaphore);
NSLog(@"%@ --%ld",type,flag);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(semaphore);
}];
long flag = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@ --%ld",type,flag);
}

方式二:dispatch_group_enter和dispatch_group_leave配合使用
- (void)requestFormData{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
[self requestWithType:@"吃飯" group:group];
});
dispatch_group_async(group, queue, ^{
[self requestWithType:@"睡覺" group:group];
});
dispatch_group_async(group, queue, ^{
[self requestWithType:@"打豆豆" group:group];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務完成");
});
}
- (void)requestWithType:(NSString *)type group:(dispatch_group_t)group{
//通知group,下面的任務馬上要放到group中執(zhí)行了
dispatch_group_enter(group);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:@"http://www.mocky.io/v2/5b6685533200006a00ee11b1" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//通知group,任務完成了,該任務要從group中移除了
dispatch_group_leave(group);
NSLog(@"%@",type);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_group_leave(group);
}];
}

如果上面對你來說太so seay了,于是在此基礎添加一個要求,吃完飯后,打會豆豆,之后便允許睡覺,才算完成任務了。革命尚未成功,同志仍需努力????
使用AFNetworking請求,實現(xiàn)線程同步以及依賴
- (void)requestFormData{
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[self requestWithType:@"吃飯"];
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[self requestWithType:@"睡覺"];
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[self requestWithType:@"打豆豆"];
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"任務完成");
}];
}];
[operation2 addDependency:operation1];
[operation4 addDependency:operation1];
[operation4 addDependency:operation2];
[operation4 addDependency:operation3];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1,operation2, operation3, operation4] waitUntilFinished:NO];
}
- (void)requestWithType:(NSString *)type {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:@"http://www.mocky.io/v2/5b6685533200006a00ee11b1" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@",type);
dispatch_semaphore_signal(semaphore);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

知識點補給站
-
addDependency不能添加相互依賴,例如:A依賴B,B依賴A,這樣會導致死鎖
創(chuàng)建信號量,可以設置信號量的資源數(shù)。0表示沒有資源。如:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0)等待信號,會讓信號量值減一,當信號量值為0時會等待(直到超時),否則正常執(zhí)行;如:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)發(fā)送一個信號,會讓信號總量加1。如:
dispatch_semaphore_signal(semaphore)
-
dispatch_group_enter必須在dispatch_group_leave之前出現(xiàn) -
dispatch_group_enter和dispatch_group_leave必須成對出現(xiàn) - 如果
dispatch_group_enter比dispatch_group_leave多一次,則wait函數(shù)等待的線程不會被喚醒和注冊notify的回調block不會執(zhí)行 - 如果
dispatch_group_leave比dispatch_group_enter多一次,則會引起崩潰。