初学iOS----多线程(一:NSThread、pathread、NSOperation、线程安全)

![多线程.2.png](http://upload-images.jianshu.io/upload_images/1803560-898b71ebec4c2912.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在说多线程之前,首先要明白线程,进程

线程和进程

进程:
  • 系统中正在运行的程序,称为一个进程
  • 每个进程,都在自己独立的内存空间运行,并且进程之间互不影响。
线程:
  • 进程的所有任务,都是在线程执行的,可以说它是进程的基本执行单元,是程序中独立运行的代码段。
  • 主线程:每个正在运行的程序(即进程),至少包含一个线程,这个线程叫做主线程
  • 单线程:只有一个主线程的程序,称作单线程程序

多线程

顾名思义,多线程就是拥有多个线程的程序,iOS用户可以根据需要在一段进程中开辟新的线程,这些线程相对于主线程来说被称为子线程,子线程和主线程可以并行(同时)执行不同的任务。

原理:

1.同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)
2.多线程并发(同时执行,其实是CPU快速地在多条线程之间)调度(切换)
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
4.多核CPU,每个核心都可以同时处理不同任务,从而真正达到了多线程并发执行任务
5.线程非常多:
1> CPU会在N多条线程之间调度,CPU会累死,消耗大量的CPU资源
2> 每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程优点

1.能适当提高程序的执行效率
2.能适当提高资源利用率(CPU、内存利用率)

多线程缺点

1.开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
2.线程越多,CPU在调度线程上的开销就越大
3.程序设计更加复杂:比如线程之间的通信,多线程的数据共享

多线程在iOS开发中的应用

1.什么是主线程
1> 一个iOS程序运行后,默认会开启一条线程,称为"主线程"或"UI线程"
2> 每一个进程都有一个独立的主线程
2.主线程的主要作用
1> 显示\刷新UI界面
2> 处理UI事件 (比如点击事件、滚动事件、拖拽事件等)
3.主线程的使用注意
1> 别将比较耗时的操作放到主线程中
2> 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的坏体验
例如:首先随便在viewController上拖一个textView或者是写一个button的点击事件,然后在viewDidLoad里写上一个稍微耗时的操作:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (int i = 0; i<10000; i++) {
        NSLog(@"---------%d", i);
    }
}

那么当点击页面后快速的滑动textView,这时会发现,它是没反应的

阻塞主线程.gif

一直等到for循环走完之后,才会有反应,继续执行操作,这就是阻塞主线程。

多线程的实现方案

技术方案 简介 语言 线程声明周期 使用频率
pathread 一套通用的多线程API</br> 适用于Unix\Linux\Windows等系统</br>跨平台\可移植</br>使用难度大 C 程序员管理 几乎不用
NSThread 使用更加面向对象</br>简单易用,可直接操作线程对象 OC 程序员管理 偶尔使用
GCD 旨在替代NSThread等线程技术</br>充分利用设备的多核 C 自动管理 经常使用
NSOperation 基于GCD(底层是GCD)</br>比GCD多了一些简单实用的功能</br>实用更加面向对象 OC 自动管理 经常使用

pthread的创建:

    //创建
    pthread_t myRestrict;
    pthread_create(&myRestrict, NULL, run, NULL);
void *run(void *data){
    NSLog(@"*******%@",[NSThread currentThread]);
    return NULL;
}

因为不常用,不做多解释,从打印结果可以看出,开辟了子线程

NSThread创建和启动线程的3种方式:

#pragma mark ---- 1.使用NSThread手动开辟子线程 ----
//    1、创建线程(NSThread)
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downLoad:) object:"http//:abcde.png"];
//线程的名字
thread.name = @"下载线程";
//    2、开启线程
        [thread start];
//    3、关闭线程(可写可不写)
        [NSThread exit];
//    4、取消线程(实际上就是做了个标记,表示被取消了)
        [thread cancel];
#pragma mark ---- 2.使用NSThread自动开辟子线程(无需手动) ----
 [NSThread detachNewThreadSelector:@selector(downLoad:) toTarget:self withObject:"http//:abcde.png"];
[thread start];
#pragma mark ---- 3.使用NSObject开辟子线程(在后台执行某个方法),也叫隐式创建(无需手动) ----
/*
 [self performSelector:@selector(downLoad:) withObject:@"http//:abcde.png"];
//相当于下面这段代码
   [self downLoad:@"http//:abcde.png"];
#因此,这个方法是不开辟子线程的
*/
[self performSelectorInBackground:@selector(downLoad:) withObject:@"http//:abcde.png"];
//这个方法开辟子线程

常见的方法:

1> 获得当前线程
+ (NSThread *)currentThread;
 2> 获得主线程
+ (NSThread *)mainThread;
 3>睡眠(暂停)线程
 +(void)sleepUntilDate:(NSDate *)date;
 + (void)sleepForTimeInterval:(NSTimeInterval)ti
------
//回到主线程刷新数据
    [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];

-(void)reloadData{
    //刷新UI
    if ([NSThread isMainThread]) {
        //这里做一些需要的操作
    }
}

利用上面的一些方法,可以做一些简单的多线程之间的通信,例如开辟子线程下载图片,回到主线程刷新,这里不再操作

多线程的安全隐患

1.资源共享
1> 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
2> 比如多个线程访问同一个对象、同一个变量、同一个文件
2.当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

安全隐患解决--加锁(互斥锁)

1.互斥锁的优缺点
1> 优点:能有效的防止多线程抢夺资源造成的数据安全问题
2> 缺点:需要消耗大量的CPU资源
2.互斥锁的使用前提:多条线程抢夺同一块资源
线程同步的意思是:多条线程实在同一条线上执行(按顺序的执行任务)
互斥锁,就是使用了线程同步技术
单纯的靠文字叙述可能会比较难理解,可以参考以下例子:写一个卖票的简单程序

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.leftTicketCount = 50;
    
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread1.name = @"1号窗口";
    
    self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread2.name = @"2号窗口";
    
    self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread3.name = @"3号窗口";
    

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.thread1 start];
    [self.thread2 start];
    [self.thread3 start];
}

/**
 *  卖票
 */
- (void)saleTicket
{
    while (1) {
      
            int count = self.leftTicketCount;
            if (count > 0) {
                [NSThread sleepForTimeInterval:0.05];
                
                self.leftTicketCount = count - 1;
                
                NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
            } else {
                return; // 退出循环
            }
    }
}

运行结果:


卖票.png

这就是多线程访问同一块资源造成的
加锁:在刚才的方法里加上@synchronized

- (void)saleTicket
{
    while (1) {
        // ()小括号里面放的是锁对象
        @synchronized(self) { // 开始加锁
            int count = self.leftTicketCount;
            if (count > 0) {
                [NSThread sleepForTimeInterval:0.05];
                
                self.leftTicketCount = count - 1;
                
                NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
            } else {
                return; // 退出循环
            }
        } // 解锁
    }
}


GCD的内容较多,我会在下篇文章里详细介绍

NSOperation

1.首先,它是一个抽象类,所以执行任务的是它的子类:NSInvocationOperation和NSBlockOperation(还有:自定义子类继承NSOperation,实现内部相应的⽅法)。这两个子类,相当于一个方法选择器“prefromSelector()”,由它俩本身发起的任务,并不是在子线程中执行。
2.NSOperation和它的子类,本身并不会进行线程的创建,所以,在他们的任务方法中打印当前线程,显示为主线程
3.NSOperation和它的子类,只是一个操作,本身没有主线程、子线程之分,可以在任何线程中使用,通常和NSOperationQueue结合使用。

NSOperationQueue

1、一个NSOperationQueue操作队列,就相当于一个线程管理器,将NSOperation和子类的对象放入队列中,然后由队列负责派发任务,所以NSOperationQueue并不是一个线程。但是,你可以设置队列中运行的线程的数量

---- 优点 ----
不需要手动关联线程,只需要把精力放在自己要执行的操作上面。

---- 缺点 ----
它是基于OC对象的,那么相对于C函数来说效率要低

配合使用NSOperation和NSOperationQueue也能实现多线程编程


//子类一:NSInvocationOperation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
//operation在单独使用的时候,需要手动调用开启方法
  [operation start];
#operation直接调用start,是同步执行(在当前线程中执行操作),说白了就相当于[self hehehe];并没有什么卵用
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
[queue addOperation:operation];//********注意:如果搭配了NSOperationQueue中的add方法创建多线程的话,就不需要使用satrt方法,否则会崩溃
//子类二:NSBlockOperation
//任务数量 > 1,才会开始异步执行
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block:%@",[NSThread currentThread]);
        NSLog(@"block:%@",[NSThread mainThread]);
        NSLog(@"block:%d",[NSThread isMainThread]);
        NSLog(@"*********");
    }];
//开启
    [blockOperation start];
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block:%@",[NSThread currentThread]);
        NSLog(@"block:%@",[NSThread mainThread]);
        NSLog(@"block:%d",[NSThread isMainThread]);
        NSLog(@"*********");
    }];
[queue addOperation:blockOperation];
//简单的做法:
 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
        NSLog(@"*******%@", [NSThread currentThread]);
    }];
#------设置最大并发数-----
 //当设置最大并发数为1时 :也可叫做串行,顺序执行
    //当设置最大并发数大于1时:叫并行,多条通道同时进行各自的任务,互不影响。
    queue.maxConcurrentOperationCount = 3;

除了GCD,其他两种主要的创建多线程的方法基本已经介绍完了,如果觉得有什么问题或者错误,欢迎留言或发简信!!

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

推荐阅读更多精彩内容