iOS之多线程-1

预备知识:

1.进程与线程

进程:进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。(进程的查看可以在活动监视器去查看)
线程:1个进程要想执行任务,必须得有线程,每个程序至少要有一条线程。一个进程中所有的任务都是在线程中执行的。如果一个线程有多个任务需要处理,这就需要一一处理这些任务,这就是是线程的串行。

线程与进程的比较:

  • 线程是CPU调用(执行任务)的最小单位。
  • 进程是CPU分配资源和调度的单位。
  • 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程。
  • 同一个进程内的线程共享进程的资源。

2.多线程

多线程:1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。
串行:如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务(也就是说线程中同一时间段只能做一件事情)。

串行图解

并行:多个线程都是执行,就是叫并行。


多线程图解

多线程原理(单个CPU的情况下):
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
线程也不是开的越多越好,开得较多的话反而会降低效率,线程一般就3~5条最好。

3.主线程

主线程:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。

主线程的作用:

  • 显示\刷新UI界面
  • 处理UI事件(比如点击事件、滚动事件、拖拽事件等)

主线程的使用注意:

  • 将耗时操作尽量不放在主线程中执行。(文件上传等都是耗时操作)
  • 耗时操作会卡住主线程,会造成UI界面的卡顿,造成不好的用户体验。

由于在线程中是串行处理的,如果一个耗时操作放在主线程中执行,如果用户点击了按钮,而和UI相关的都是放在主线程的,所以只有当耗时操作完成后才会去执行按钮的点击事件,这样就会造成卡顿,用户体验不好。
耗时操作优化处理的方式:将耗时操作放在子线程(非主线程,后台线程)中进行处理。
由于子线程和主线程是同时执行的,这样耗时操作放在子线程中执行的,在点击按钮的时候,就不会造成卡顿的现象出现。

4.多线程的实现方案

多线程的实现方案
  • pthread

- (IBAction)btnClickAction:(id)sender {
    // 创建线程对象
    pthread_t thread;
    
    // 创建线程
    
    /**
     创建线程

     @param _Nonnull#> 线程对象传递地址 description#>
     @param _Nullable#> 线程的属性(优先级等) description#>
     @param _Nonnull 指向函数的指针
     @return 函数需要传递的函数
     */
    pthread_create(&thread, nil, task, NULL); // 创建一条线程,如果要创建多个线程就可以将上面的代码copy下就可以了。
 
    pthread_t threadB;
    pthread_create(&threadB, nil, task, NULL); // 第二个线程
    
    pthread_equal(thread, threadB); // 判断两个线程是不是相等。
}


void * task(void *parameter) {
    NSLog(@"%@ ---- ", [NSThread currentThread]);
    // 可以将耗时操作放在这里执行。
    return NULL;
}

// 打印结果
2017-08-06 22:47:48.854 1-pthread[4855:328489] <NSThread: 0x608000266d40>{number = 3, name = (null)} ---- 
2017-08-06 22:47:48.854 1-pthread[4855:328490] <NSThread: 0x608000265b80>{number = 4, name = (null)} ---- 
  • NSThread

创建线程的几个方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self creatNewThreadMethod];
}

- (void)creatNewThreadMethod {
    // 开启一条后台线程
    [self performSelectorInBackground:@selector(run:) withObject:@"开启后台子线程"];
}

- (void)creatDetachNewThreadMethod {
    // 创建分离子线程->会自动启动子线程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
}

- (void)creatNewThread {
    
    // 创建线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    // 启动线程,默认是暂停状态的
    [thread start];
    
}

- (void)run:(NSString *)param {
    NSLog(@"---run--%@", [NSThread currentThread]);
}

设置线程的名字以及优先级等属性

// 创建多个线程->给每个线程命名以及优先级
- (void)creatMutilThread {
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    thread1.name = @"thread 1";
    thread1.threadPriority = 0.6; // 设置优先级(0.0~1.0,默认的优先级是0.5)
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    thread2.name = @"thread 2";
    thread2.threadPriority = 0.3;
    NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    thread3.name = @"thread 3";
    thread3.threadPriority = 1.0;
    [thread1 start];
    [thread2 start];
    [thread3 start];
}

- (void)run:(NSString *)param {
    for (int i = 0; i < 10; i++) {
        NSLog(@"---run--%@", [NSThread currentThread].name);
    }
}
// 打印结果
2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.490 2-NSThread基本使用[5136:356390] ---run--thread 3
2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.492 2-NSThread基本使用[5136:356388] ---run--thread 1
2017-08-06 23:11:51.493 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.495 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.496 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.496 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.496 2-NSThread基本使用[5136:356389] ---run--thread 2
2017-08-06 23:11:51.497 2-NSThread基本使用[5136:356389] ---run--thread 2
打印次数越多,打印的数字就越趋于准确的值。
  • 线程状态
线程状态图解

线程的控制状态等操作:

// 启动
- (void)start;
// 阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 强制停止线程
+ (void)exit; // 一旦线程停止(死亡)了,就不能再次开启任务
// 和break不一致,表示任务执行完毕后才退出的。

5.多线程安全隐患

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


同一时间存取钱

以上的图展示存取钱是同时处理的,造成了最终的钱变少了,造成了数据的不当处理。
处理的方案
苹果的文档说明的解决方案就是加上一个互斥锁,在访问数据的时候加上互斥锁,只要次个线程才能访问处理,在访问结束的时候打开把互斥锁打开,然后另一个线程进行访问,同样加上互斥锁,循环如此,这样就解决了数据安全的隐患。


互斥锁
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.totalCount = 100;
    self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadA.name = @"售票员A";
    self.threadB.name = @"售票员B";
    self.threadC.name = @"售票员C";
    [self.threadA start];
    [self.threadB start];
    [self.threadC start];
}

- (void)saleTicket {
    while (1) {
        // 锁必须是全局唯一的。
        // 1.注意点,不能随便能乱加,加锁的位置要注意
        // 2.加锁的前提条件,--多线程共享同一个资源
        // 3.注意加锁是需要代价的--需要耗费性能的
        // 4.加锁的结果是造成线程同步。->ABC ABC ABC的循环执行任务
        @synchronized (self) {
            NSInteger count = self.totalCount;
            if (count > 0) {
                self.totalCount = count - 1;
                // 卖出去一张票,还剩下多少张
                NSLog(@"%@卖出去一张票, 还剩下%ld张", [NSThread currentThread].name, self.totalCount);
            } else {
                NSLog(@"没票了");
                break;
            }
        }
    }  
}

6.原子和非原子性

在oc中定义属性时有nonatomic和atomic两种选择;
atomic:原子属性,为setter方法加锁(默认就是atomic),意味着线程是安全的。
nonatomic:非原子属性,不会为setter方法加锁。

7.线程间的通信

有的时候,同一个进程跑了多个线程,有的子线程的输出的结果是另一个子线程的输入,这就需要两个子线程中进行一种通信。所以线程往往不是孤立存在的,多个线程之间需要经常进行通信。
线程之间的通信的方法:

// 回到主线程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 回到线程对象,可以回到主线程也可以回到子线程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

例子:下载图片并展示
如果不做线程的处理,


- (void)wjDownloadImageCountTime {
    NSDate *start = [NSDate date]; // 获得当前时间
    NSURL *url = [NSURL URLWithString:@"http://pic1.win4000.com/wallpaper/d/573d37cc14e3c.jpg"];
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *img = [UIImage imageWithData:imageData];
    self.imageView.image = [img imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    NSDate *end = [NSDate date];
    NSLog(@"count time is: %f", [end timeIntervalSinceDate:start]);
}

以上能够完成图片的下载,图片的展示也能成功,但是一旦UI上的图片比较多的话,就会阻塞主线程,所以不采取。
优化的处理


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 放在子线程去下载图片
    [NSThread detachNewThreadSelector:@selector(wjDownloadImage) toTarget:self withObject:nil];

}

- (void)wjDownloadImage {
    // http://img4.imgtn.bdimg.com/it/u=816246739,294523191&fm=214&gp=0.jpg
    // 1.下载图片的url
    NSURL *url = [NSURL URLWithString:@"http://pic1.win4000.com/wallpaper/d/573d37cc14e3c.jpg"];
    // 2.下载图片到本地->二进制数据 -> 耗时操作
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    // 3.将二进制数据转为图片
    UIImage *img = [UIImage imageWithData:imageData];
    // 4.显示UI->回到主线程去刷新UI界面
    [self performSelectorOnMainThread:@selector(wjShowImage:) withObject:img waitUntilDone:YES];
//  简便方法,就不需要写下面的那个方法了。
//    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:img waitUntilDone:YES];
}

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

推荐阅读更多精彩内容

  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,736评论 0 17
  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,119评论 0 23
  • 欢迎大家指出文章中需要改正或者需要补充的地方,我会及时更新,非常感谢。 一. 多线程基础 1. 进程 进程是指在系...
    xx_cc阅读 7,184评论 11 70
  • 一、多线程 说明下线程的状态 java中的线程一共有 5 种状态。 NEW:这种情况指的是,通过 New 关键字创...
    Java旅行者阅读 4,676评论 0 44
  • 步骤: 附录 用pivix画完,再把原图和画好的图放到sketchclub 中对比,相似度大概70%吧 补充说明:...
    clfcool阅读 788评论 21 4