iOS中多線程pthread與NSThread

總結(jié)ios開(kāi)發(fā)中一些知識(shí),有錯(cuò)或者有問(wèn)題的歡迎交流。

這些資料也是看其他的一些文章總結(jié)的,首先把別人文章地址貼出來(lái)。本文直接介紹pthread和NSThread,其他多線程方式請(qǐng)看本人其他文章或者其他地方了解。

ios-Pthread?

?iOS多線程-pthread,NSThread

一 ?pthread

基本介紹

????pthread 是 POSIX 多線程開(kāi)發(fā)框架。跨平臺(tái),適用于多種操作系統(tǒng),可移植性強(qiáng),是一套純C語(yǔ)言的通用API,且線程的生命周期需要程序員自己管理。

?????ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理,而不負(fù)責(zé) C 語(yǔ)言 代碼的內(nèi)存管理。因此,開(kāi)發(fā)過(guò)程中,如果使用的 C 語(yǔ)言框架出現(xiàn)retain/create/copy/new 等字樣的函數(shù),大多都需要 release,否則會(huì)出現(xiàn)內(nèi)存泄漏。在混合開(kāi)發(fā)時(shí),如果在 C 和 OC 之間傳遞數(shù)據(jù),需要使用 __bridge 進(jìn)行橋接,橋接的目的就是為了告訴編譯器如何管理內(nèi)存。

橋接的添加可以借助 Xcode 的輔助功能添加。

MRC 中不需要使用橋接。

使用方法

使用pthread 要引入頭文件

#import <pthread.h>

創(chuàng)建線程,并在線程中執(zhí)行demo

/** pthread_create(&threadId, NULL, demo, (__bridge void *)(str));

參數(shù):

1> 指向線程標(biāo)識(shí)符的指針,C 語(yǔ)言中類(lèi)型的結(jié)尾通常 _t/Ref,而且不需要使用 *

2> 用來(lái)設(shè)置線程屬性

3> 線程運(yùn)行函數(shù)的起始地址

4> 運(yùn)行函數(shù)的參數(shù)

返回值: - 若線程創(chuàng)建成功,則返回0 - 若線程創(chuàng)建失敗,則返回出錯(cuò)編號(hào)

*/

pthread_t threadId = NULL; //一個(gè)結(jié)構(gòu)體,具有指向了當(dāng)前線程的指針

NSString *str = @"Hello Pthread";

// 這邊的demo函數(shù)名作為第三個(gè)參數(shù)寫(xiě)在這里可以在其前面加一個(gè)&,也可以不加,因?yàn)楹瘮?shù)名就代表了函數(shù)的地址。

int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));

if (result == 0) {

NSLog(@"創(chuàng)建線程 OK");

} else {

NSLog(@"創(chuàng)建線程失敗 %d", result);

}

// pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(jié)束后會(huì)自動(dòng)釋放所有資源。

pthread_detach(threadId);


回調(diào)函數(shù)

// 后臺(tái)線程調(diào)用函數(shù)

void *demo(void *params) {

NSString *str = (__bridge NSString *)(params);

NSLog(@"%@ - %@", [NSThread currentThread], str);

return NULL;

} ?

函數(shù)介紹

pthread_create的函數(shù)原型為 ? ?

int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict, void *(*)(void *), void * __restrict);

第一個(gè)參數(shù)pthread_t * __restrict

由于c語(yǔ)言沒(méi)有對(duì)象的概念,所以pthread_t實(shí)際是一個(gè)結(jié)構(gòu)體

所以創(chuàng)建的thread是一個(gè)指向當(dāng)前新建線程的指針

typedef __darwin_pthread_t pthread_t;

typedef struct _opaque_pthread_t *__darwin_pthread_t;

struct _opaque_pthread_t {

long __sig;

struct __darwin_pthread_handler_rec *__cleanup_stack;

char __opaque[__PTHREAD_SIZE__];

}; ?

第二個(gè)參數(shù)const pthread_attr_t * __restrict

同樣是一個(gè)結(jié)構(gòu)體,這里是用來(lái)設(shè)置線程屬性的

typedef __darwin_pthread_attr_t pthread_attr_t;

typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;

struct _opaque_pthread_attr_t {

long __sig;

char __opaque[__PTHREAD_ATTR_SIZE__];

}; ?

第三個(gè)參數(shù)void?()(void *)這里給出了一個(gè)函數(shù)指針,指向的是一個(gè)函數(shù)的起始地址,所以是線程開(kāi)啟后的回調(diào)函數(shù) ? ?

第四個(gè)參數(shù)是回調(diào)函數(shù)所用的參數(shù) ? ?

這里只是介紹了pthread的基本用法和其參數(shù)類(lèi)型,和其他多線程方式比除了跨平臺(tái)還有什么優(yōu)點(diǎn),蘋(píng)果提供pthread具體在項(xiàng)目中怎么使用,希望有做過(guò)的大神留言溝通,我也會(huì)繼續(xù)更新相關(guān)內(nèi)容。


二 NSThread ? 原文在這里

通過(guò)NSThread我們具體討論一些線程相關(guān)的問(wèn)題,包括如下內(nèi)容:

使用NSThread創(chuàng)建線程

線程狀態(tài)

線程間通信

線程安全

1. 使用NSThread創(chuàng)建線程

使用NSThread創(chuàng)建線程有以下幾種方式:

使用NSThread的init方法顯式創(chuàng)建

使用NSThread類(lèi)方法顯式創(chuàng)建并啟動(dòng)線程

隱式創(chuàng)建并啟動(dòng)線程

具體的代碼實(shí)現(xiàn)在下面已經(jīng)給出了,這里提醒大家注意一點(diǎn)。只有使用NSThread的init方法創(chuàng)建的線程才會(huì)返回具體的線程實(shí)例。也就是說(shuō)如果想要對(duì)線程做更多的控制,比如添加線程的名字、更改優(yōu)先級(jí)等操作,要使用第一種方式來(lái)創(chuàng)建線程。但是此種方法需要使用start方法來(lái)手動(dòng)啟動(dòng)線程。

/** * 隱式創(chuàng)建并啟動(dòng)線程 */

- (void)createThreadWithImplicit {

// 隱式創(chuàng)建并啟動(dòng)線程

[self performSelectorInBackground:@selector(threadMethod3:) withObject:@"implicitMethod"];

}

/** * 使用NSThread類(lèi)方法顯式創(chuàng)建并啟動(dòng)線程 */

- (void)createThreadWithClassMethod {

// 使用類(lèi)方法創(chuàng)建線程并自動(dòng)啟動(dòng)線程

[NSThread detachNewThreadSelector:@selector(threadMethod2:) toTarget:self withObject:@"fromClassMethod"];

}

/** * 使用init方法顯式創(chuàng)建線程 */

- (void)createThreadWithInit {

// 創(chuàng)建線程

NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod1) object:nil];

// 設(shè)置線程名

[thread1 setName:@"thread1"];

// 設(shè)置優(yōu)先級(jí) 優(yōu)先級(jí)從0到1 1最高

[thread1 setThreadPriority:0.9];

// 啟動(dòng)線程

[thread1 start];

} ?

2:線程狀態(tài)

線程狀態(tài)分為:啟動(dòng)線程,阻塞線程,結(jié)束線程?

啟動(dòng)線程: ??

- (void)start;

阻塞線程:

// 線程休眠到某一時(shí)刻

+ (void)sleepUntilDate:(NSDate *)date;

// 線程休眠多久

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

結(jié)束線程:

+ (void)exit;

大家在看官方api的時(shí)候可能會(huì)有一個(gè)疑問(wèn),api里明明有cancel方法,為什么使用cancel方法不能結(jié)束線程?

當(dāng)我們使用cancel方法時(shí),只是改變了線程的狀態(tài)標(biāo)識(shí),并不能結(jié)束線程,所以我們要配合isCancelled方法進(jìn)行使用。具體實(shí)現(xiàn)如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

// 創(chuàng)建線程

[self createThread];

}

- (void)createThread {

// 創(chuàng)建線程

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod) object:nil];

thread.name = @"i'm a new thread";

// 啟動(dòng)線程

[thread start];

}

/** * 線程方法 */

- (void)threadMethod {

NSLog(@"thread is create -- the name is: \"%@\"", [NSThread currentThread].name);

// 線程阻塞 -- 延遲到某一時(shí)刻 --- 這里的時(shí)刻是3秒以后

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];

NSLog(@"sleep end");

NSLog(@"sleep again"); // 線程阻塞 -- 延遲多久 -- 這里延遲2秒

[NSThread sleepForTimeInterval:2];

NSLog(@"sleep again end");

for (int i = 0 ; i < 100; i++) {

NSLog(@"thread working");

if(30 == i) {

NSLog(@"thread will dead");

[[NSThread currentThread] cancel];

}

if([[NSThread currentThread] isCancelled]) {

// 結(jié)束線程//

[NSThread exit]; return;

} }} ?

3:線程間通訊

線程間通信我們最常用的就是開(kāi)啟子線程進(jìn)行耗時(shí)操作,操作完畢后回到主線程,進(jìn)行數(shù)據(jù)賦值以及刷新主線程UI。在這里,用一個(gè)經(jīng)典的圖片下載demo進(jìn)行簡(jiǎn)述。首先我們先了解一下api給出的線程間通信的方法:

//與主線程通信

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;

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

// equivalent to the first method with kCFRunLoopCommonModes//

與其他子線程通信

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); ?

以下是demo

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

// 下載圖片

[self downloadImage];

}

/** * 下載圖片 */

- (void)downloadImage {

// 創(chuàng)建線程下載圖片

[NSThread detachNewThreadSelector:@selector(downloadImageInThread) toTarget:self withObject:nil];

}

/** * 線程中下載圖片操作 */

- (void)downloadImageInThread {

NSLog(@"come in sub thread -- %@", [NSThread currentThread]);

// 獲取圖片url

NSURL *url = [NSURL URLWithString:@"http://img.ycwb.com/news/attachement/jpg/site2/20110226/90fba60155890ed3082500.jpg"];

// 計(jì)算耗時(shí)

NSDate *begin = [NSDate date];

// 使用CoreFoundation計(jì)算耗時(shí) CFDate

CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();

// 從url讀取數(shù)據(jù)(下載圖片) -- 耗時(shí)操作

NSData *imageData = [NSData dataWithContentsOfURL:url];

NSDate *end = [NSDate date];

CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();

// 計(jì)算時(shí)間差

NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);

NSLog(@"time difference inCF -- %f", endInCF - beginInCF);

// 通過(guò)二進(jìn)制data創(chuàng)建image

UIImage *image = [UIImage imageWithData:imageData];

// 回到主線程進(jìn)行圖片賦值和界面刷新

[self performSelectorOnMainThread:@selector(backToMainThread:) withObject:image waitUntilDone:YES];

// 這里也可以使用imageView的set方法進(jìn)行操作//

[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

}

/** * 回到主線程的操作 */

- (void)backToMainThread:(UIImage *)image {

NSLog(@"back to main thread --- %@", [NSThread currentThread]);

// 賦值圖片到imageview

self.imageView.image = image;

} ?

在demo中已經(jīng)把注釋寫(xiě)的比較清晰了,需要補(bǔ)充的有三點(diǎn):

1.performSelectorOnMainThread:withObject:waitUntilDone:方法這里是回到了主線程進(jìn)行操作,同樣也可以使用

[selfperformSelector:@selector(backToMainThread:) onThread:[NSThreadmainThread] withObject:image waitUntilDone:YES];

回到主線程,或者進(jìn)入其他線程進(jìn)行操作。

2.在實(shí)際項(xiàng)目中我們可能會(huì)分析耗時(shí)操作所花費(fèi)時(shí)間或者分析用戶(hù)行為的時(shí)候要計(jì)算用戶(hù)在當(dāng)前頁(yè)面所耗時(shí)間,所以在demo中加入了時(shí)間的兩種計(jì)算方式,分別是CoreFoundation和Foundation中的。

// 計(jì)算耗時(shí)

NSDate *begin = [NSDate date];

// 使用CoreFoundation計(jì)算耗時(shí) CFDate

CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();

// 從url讀取數(shù)據(jù)(下載圖片) -- 耗時(shí)操作

NSData *imageData = [NSData dataWithContentsOfURL:url];

NSDate *end = [NSDate date];

CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();

// 計(jì)算時(shí)間差 NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);

NSLog(@"time difference inCF -- %f", endInCF - beginInCF); ?

3.如果自己寫(xiě)的項(xiàng)目無(wú)法運(yùn)行,可能是因?yàn)閄code7 創(chuàng)建HTTP請(qǐng)求報(bào)錯(cuò)導(dǎo)致,具體解決方案請(qǐng)點(diǎn)擊這里。

4:線程安全

因?yàn)槭嵌嗑€程操作,所以會(huì)存在一定的安全隱患。原因是多線程會(huì)存在不同線程的資源共享,也就是說(shuō)我們可能在同一時(shí)刻兩個(gè)線程同時(shí)操作了某一個(gè)變量的值,但是線程的對(duì)變量的操作不同,導(dǎo)致變量的值出現(xiàn)誤差。下面是一個(gè)存取錢(qián)的demo片段:

- (void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

// 初始化狀態(tài) [self initStatus];

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

// 啟動(dòng)線程 [self startThread];

}

/** * 初始化狀態(tài) */

- (void)initStatus {

// 設(shè)置存款

self.depositMoney = 5000;

// 創(chuàng)建存取錢(qián)線程

self.saveThread = [[NSThread alloc] initWithTarget:self selector:@selector(saveAndDraw) object:nil];

self.saveThread.name = @"save";

self.drawThread = [[NSThread alloc] initWithTarget:self selector:@selector(saveAndDraw) object:nil];

self.drawThread.name = @"draw";

}

/** * 開(kāi)啟線程 */

- (void)startThread {

// 開(kāi)啟存取錢(qián)線程

[self.saveThread start];

[self.drawThread start];

}

/** * 存取錢(qián)操作 */

- (void)saveAndDraw {

while(1) {

if(self.depositMoney > 3000) {

// 阻塞線程,模擬操作花費(fèi)時(shí)間

[NSThread sleepForTimeInterval:0.05];

if([[NSThread currentThread].name isEqualToString:@"save"]) {

self.depositMoney += 100;

} else {

self.depositMoney -= 100;

}

NSLog(@"currentThread: %@, depositMoney: %d", [NSThread currentThread].name, self.depositMoney);

} else {

NSLog(@"no money"); return;

} }}?

在上面的demo中我們發(fā)現(xiàn),存取錢(qián)的線程是同時(shí)開(kāi)啟的,而存取錢(qián)的錢(qián)數(shù)相同,所以每一次存取操作結(jié)束后,存款值應(yīng)該不會(huì)改變。大家可以運(yùn)行demo進(jìn)行查看結(jié)果。

所以需要在線程操作中加入鎖:

/** * 存取錢(qián)操作 */

- (void)saveAndDraw {

while(1) {

// 互斥鎖

@synchronized (self) {

if(self.depositMoney > 3000) {

// 阻塞線程,模擬操作花費(fèi)時(shí)間

[NSThread sleepForTimeInterval:0.05];

if([[NSThread currentThread].name isEqualToString:@"save"]) {

self.depositMoney += 100;

} else {

self.depositMoney -= 100;

}

NSLog(@"currentThread: %@, depositMoney: %d", [NSThread currentThread].name, self.depositMoney);

} else {

NSLog(@"no money"); return;

} } }} ?

線程安全解決方案 這邊有介紹關(guān)于鎖的一些文章可以看看?

ios開(kāi)發(fā)中8種鎖? ?關(guān)于 @synchronized

* 互斥鎖@synchronized 的作用是創(chuàng)建一個(gè)互斥鎖,保證此時(shí)沒(méi)有其它線程對(duì)鎖住的對(duì)象進(jìn)行修改。

* 互斥鎖使用格式:@synchronized (鎖對(duì)象) { // 需要鎖定的代碼 }

* 互斥鎖的優(yōu)缺點(diǎn):

優(yōu)點(diǎn): 防止多線程對(duì)共享資源進(jìn)行搶奪造成的數(shù)據(jù)安全問(wèn)題

缺點(diǎn): 需要消耗大量cpu資源 ?

atomic屬性?xún)?nèi)部的鎖稱(chēng)為 自旋鎖

互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖。一旦被訪問(wèn)的資源被解鎖,則等待資源的線程會(huì)被喚醒。 ? ?

自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)以死循環(huán)的方式等待鎖,一旦被訪問(wèn)的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行。 ? ?

自旋鎖的效率高于互斥鎖。

NSThread頭文件中的相關(guān)信息請(qǐng)通過(guò)command + 鼠標(biāo)左鍵的方式查看

總結(jié)?

pthread的線程方式雖然平時(shí)沒(méi)用到,但是也有其自己特點(diǎn),希望了解的大佬能留言溝通。

NSThread平時(shí)開(kāi)發(fā)中,時(shí)常用來(lái)進(jìn)行線程間通訊,AFN中也用來(lái)開(kāi)啟一個(gè)常駐線程。

還沒(méi)來(lái)得及自己去實(shí)驗(yàn)總結(jié) 如果有錯(cuò)誤希望能指出來(lái)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容