iOS中多线程pthread与NSThread

总结ios开发中一些知识,有错或者有问题的欢迎交流。

这些资料也是看其他的一些文章总结的,首先把别人文章地址贴出来。本文直接介绍pthread和NSThread,其他多线程方式请看本人其他文章或者其他地方了解。

ios-Pthread 

 iOS多线程-pthread,NSThread

一  pthread

基本介绍

    pthread 是 POSIX 多线程开发框架。跨平台,适用于多种操作系统,可移植性强,是一套纯C语言的通用API,且线程的生命周期需要程序员自己管理。

     ARC 只负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理。因此,开发过程中,如果使用的 C 语言框架出现retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏。在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,桥接的目的就是为了告诉编译器如何管理内存。

桥接的添加可以借助 Xcode 的辅助功能添加。

MRC 中不需要使用桥接。

使用方法

使用pthread 要引入头文件

#import <pthread.h>

创建线程,并在线程中执行demo

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

参数:

1> 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *

2> 用来设置线程属性

3> 线程运行函数的起始地址

4> 运行函数的参数

返回值: - 若线程创建成功,则返回0 - 若线程创建失败,则返回出错编号

*/

pthread_t threadId = NULL; //一个结构体,具有指向了当前线程的指针

NSString *str = @"Hello Pthread";

// 这边的demo函数名作为第三个参数写在这里可以在其前面加一个&,也可以不加,因为函数名就代表了函数的地址。

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

if (result == 0) {

NSLog(@"创建线程 OK");

} else {

NSLog(@"创建线程失败 %d", result);

}

// pthread_detach:设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

pthread_detach(threadId);


回调函数

// 后台线程调用函数

void *demo(void *params) {

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

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

return NULL;

}  

函数介绍

pthread_create的函数原型为    

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

第一个参数pthread_t * __restrict

由于c语言没有对象的概念,所以pthread_t实际是一个结构体

所以创建的thread是一个指向当前新建线程的指针

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__];

};  

第二个参数const pthread_attr_t * __restrict

同样是一个结构体,这里是用来设置线程属性的

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__];

};  

第三个参数void ()(void *)这里给出了一个函数指针,指向的是一个函数的起始地址,所以是线程开启后的回调函数    

第四个参数是回调函数所用的参数    

这里只是介绍了pthread的基本用法和其参数类型,和其他多线程方式比除了跨平台还有什么优点,苹果提供pthread具体在项目中怎么使用,希望有做过的大神留言沟通,我也会继续更新相关内容。


二 NSThread   原文在这里

通过NSThread我们具体讨论一些线程相关的问题,包括如下内容:

使用NSThread创建线程

线程状态

线程间通信

线程安全

1. 使用NSThread创建线程

使用NSThread创建线程有以下几种方式:

使用NSThread的init方法显式创建

使用NSThread类方法显式创建并启动线程

隐式创建并启动线程

具体的代码实现在下面已经给出了,这里提醒大家注意一点。只有使用NSThread的init方法创建的线程才会返回具体的线程实例。也就是说如果想要对线程做更多的控制,比如添加线程的名字、更改优先级等操作,要使用第一种方式来创建线程。但是此种方法需要使用start方法来手动启动线程。

/** * 隐式创建并启动线程 */

- (void)createThreadWithImplicit {

// 隐式创建并启动线程

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

}

/** * 使用NSThread类方法显式创建并启动线程 */

- (void)createThreadWithClassMethod {

// 使用类方法创建线程并自动启动线程

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

}

/** * 使用init方法显式创建线程 */

- (void)createThreadWithInit {

// 创建线程

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

// 设置线程名

[thread1 setName:@"thread1"];

// 设置优先级 优先级从0到1 1最高

[thread1 setThreadPriority:0.9];

// 启动线程

[thread1 start];

}  

2:线程状态

线程状态分为:启动线程,阻塞线程,结束线程 

启动线程:   

- (void)start;

阻塞线程:

// 线程休眠到某一时刻

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

// 线程休眠多久

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

结束线程:

+ (void)exit;

大家在看官方api的时候可能会有一个疑问,api里明明有cancel方法,为什么使用cancel方法不能结束线程?

当我们使用cancel方法时,只是改变了线程的状态标识,并不能结束线程,所以我们要配合isCancelled方法进行使用。具体实现如下:

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

// 创建线程

[self createThread];

}

- (void)createThread {

// 创建线程

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

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

// 启动线程

[thread start];

}

/** * 线程方法 */

- (void)threadMethod {

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

// 线程阻塞 -- 延迟到某一时刻 --- 这里的时刻是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]) {

// 结束线程//

[NSThread exit]; return;

} }}  

3:线程间通讯

线程间通信我们最常用的就是开启子线程进行耗时操作,操作完毕后回到主线程,进行数据赋值以及刷新主线程UI。在这里,用一个经典的图片下载demo进行简述。首先我们先了解一下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 {

// 创建线程下载图片

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

// 计算耗时

NSDate *begin = [NSDate date];

// 使用CoreFoundation计算耗时 CFDate

CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();

// 从url读取数据(下载图片) -- 耗时操作

NSData *imageData = [NSData dataWithContentsOfURL:url];

NSDate *end = [NSDate date];

CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();

// 计算时间差

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

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

// 通过二进制data创建image

UIImage *image = [UIImage imageWithData:imageData];

// 回到主线程进行图片赋值和界面刷新

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

// 这里也可以使用imageView的set方法进行操作//

[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中已经把注释写的比较清晰了,需要补充的有三点:

1.performSelectorOnMainThread:withObject:waitUntilDone:方法这里是回到了主线程进行操作,同样也可以使用

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

回到主线程,或者进入其他线程进行操作。

2.在实际项目中我们可能会分析耗时操作所花费时间或者分析用户行为的时候要计算用户在当前页面所耗时间,所以在demo中加入了时间的两种计算方式,分别是CoreFoundation和Foundation中的。

// 计算耗时

NSDate *begin = [NSDate date];

// 使用CoreFoundation计算耗时 CFDate

CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();

// 从url读取数据(下载图片) -- 耗时操作

NSData *imageData = [NSData dataWithContentsOfURL:url];

NSDate *end = [NSDate date];

CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();

// 计算时间差 NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);

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

3.如果自己写的项目无法运行,可能是因为Xcode7 创建HTTP请求报错导致,具体解决方案请点击这里

4:线程安全

因为是多线程操作,所以会存在一定的安全隐患。原因是多线程会存在不同线程的资源共享,也就是说我们可能在同一时刻两个线程同时操作了某一个变量的值,但是线程的对变量的操作不同,导致变量的值出现误差。下面是一个存取钱的demo片段:

- (void)viewDidLoad {

[super viewDidLoad];

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

// 初始化状态 [self initStatus];

}

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

// 启动线程 [self startThread];

}

/** * 初始化状态 */

- (void)initStatus {

// 设置存款

self.depositMoney = 5000;

// 创建存取钱线程

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) {

// 阻塞线程,模拟操作花费时间

[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中我们发现,存取钱的线程是同时开启的,而存取钱的钱数相同,所以每一次存取操作结束后,存款值应该不会改变。大家可以运行demo进行查看结果。

所以需要在线程操作中加入锁:

/** * 存取钱操作 */

- (void)saveAndDraw {

while(1) {

// 互斥锁

@synchronized (self) {

if(self.depositMoney > 3000) {

// 阻塞线程,模拟操作花费时间

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

} } }}  

线程安全解决方案 这边有介绍关于锁的一些文章可以看看 

ios开发中8种锁   关于 @synchronized

* 互斥锁@synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对锁住的对象进行修改。

* 互斥锁使用格式:@synchronized (锁对象) { // 需要锁定的代码 }

* 互斥锁的优缺点:

优点: 防止多线程对共享资源进行抢夺造成的数据安全问题

缺点: 需要消耗大量cpu资源  

atomic属性内部的锁称为 自旋锁

互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。    

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。    

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

NSThread头文件中的相关信息请通过command + 鼠标左键的方式查看

总结 

pthread的线程方式虽然平时没用到,但是也有其自己特点,希望了解的大佬能留言沟通。

NSThread平时开发中,时常用来进行线程间通讯,AFN中也用来开启一个常驻线程。

还没来得及自己去实验总结 如果有错误希望能指出来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容