iOS中多線程pthread與NSThread

總結(jié)ios開發(fā)中一些知識呛凶,有錯(cuò)或者有問題的歡迎交流罐呼。

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

ios-Pthread?

?iOS多線程-pthread,NSThread

一 ?pthread

基本介紹

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

?????ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理来吩,而不負(fù)責(zé) C 語言 代碼的內(nèi)存管理敢辩。因此,開發(fā)過程中弟疆,如果使用的 C 語言框架出現(xiàn)retain/create/copy/new 等字樣的函數(shù)戚长,大多都需要 release,否則會(huì)出現(xiàn)內(nè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)識符的指針,C 語言中類型的結(jié)尾通常 _t/Ref讼育,而且不需要使用 *

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

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

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

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

*/

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

NSString *str = @"Hello Pthread";

// 這邊的demo函數(shù)名作為第三個(gè)參數(shù)寫在這里可以在其前面加一個(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語言沒有對象的概念晦鞋,所以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)體,這里是用來設(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ù)的起始地址悠垛,所以是線程開啟后的回調(diào)函數(shù) ? ?

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

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


二 NSThread ? 原文在這里

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

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

線程狀態(tài)

線程間通信

線程安全

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

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

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

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

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

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

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

- (void)createThreadWithImplicit {

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

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

}

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

- (void)createThreadWithClassMethod {

// 使用類方法創(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)先級 優(yōu)先級從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è)疑問拓瞪,api里明明有cancel方法缴罗,為什么使用cancel方法不能結(jié)束線程?

當(dāng)我們使用cancel方法時(shí)祭埂,只是改變了線程的狀態(tài)標(biāo)識面氓,并不能結(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:線程間通訊

線程間通信我們最常用的就是開啟子線程進(jìn)行耗時(shí)操作舌界,操作完畢后回到主線程,進(jìn)行數(shù)據(jù)賦值以及刷新主線程UI泰演。在這里呻拌,用一個(gè)經(jīng)典的圖片下載demo進(jì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);

// 通過二進(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)把注釋寫的比較清晰了睦焕,需要補(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í)間或者分析用戶行為的時(shí)候要計(jì)算用戶在當(dāng)前頁面所耗時(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àng)目無法運(yùn)行初家,可能是因?yàn)閄code7 創(chuàng)建HTTP請求報(bào)錯(cuò)導(dǎo)致偎窘,具體解決方案請點(diǎn)擊這里

4:線程安全

因?yàn)槭嵌嗑€程操作溜在,所以會(huì)存在一定的安全隱患陌知。原因是多線程會(huì)存在不同線程的資源共享,也就是說我們可能在同一時(shí)刻兩個(gè)線程同時(shí)操作了某一個(gè)變量的值掖肋,但是線程的對變量的操作不同仆葡,導(dǎo)致變量的值出現(xiàn)誤差。下面是一個(gè)存取錢的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)建存取錢線程

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";

}

/** * 開啟線程 */

- (void)startThread {

// 開啟存取錢線程

[self.saveThread start];

[self.drawThread start];

}

/** * 存取錢操作 */

- (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)浙芙,存取錢的線程是同時(shí)開啟的,而存取錢的錢數(shù)相同籽腕,所以每一次存取操作結(jié)束后,存款值應(yīng)該不會(huì)改變纸俭。大家可以運(yùn)行demo進(jìn)行查看結(jié)果皇耗。

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

/** * 存取錢操作 */

- (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開發(fā)中8種鎖? ?關(guān)于 @synchronized

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

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

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

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

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

atomic屬性內(nèi)部的鎖稱為 自旋鎖

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

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

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

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

總結(jié)?

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市臼疫,隨后出現(xiàn)的幾起案子线召,更是在濱河造成了極大的恐慌,老刑警劉巖多矮,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缓淹,死亡現(xiàn)場離奇詭異,居然都是意外死亡塔逃,警方通過查閱死者的電腦和手機(jī)讯壶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來湾盗,“玉大人伏蚊,你說我怎么就攤上這事「穹啵” “怎么了躏吊?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長帐萎。 經(jīng)常有香客問我比伏,道長,這世上最難降的妖魔是什么疆导? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任赁项,我火速辦了婚禮,結(jié)果婚禮上澈段,老公的妹妹穿的比我還像新娘悠菜。我一直安慰自己,他們只是感情好败富,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布悔醋。 她就那樣靜靜地躺著,像睡著了一般兽叮。 火紅的嫁衣襯著肌膚如雪芬骄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天充择,我揣著相機(jī)與錄音德玫,去河邊找鬼。 笑死椎麦,一個(gè)胖子當(dāng)著我的面吹牛宰僧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼琴儿,長吁一口氣:“原來是場噩夢啊……” “哼段化!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起造成,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤显熏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后晒屎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喘蟆,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年鼓鲁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蕴轨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骇吭,死狀恐怖橙弱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燥狰,我是刑警寧澤棘脐,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站龙致,受9級特大地震影響蛀缝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜净当,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一内斯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧像啼,春花似錦、人聲如沸潭苞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽此疹。三九已至僧诚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝗碎,已是汗流浹背湖笨。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹦骑,地道東北人慈省。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像眠菇,于是被迫代替她去往敵國和親边败。 傳聞我的和親對象是個(gè)殘疾皇子袱衷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容