多线程编程-001-NSThread

1.什么是进程

进程: 系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

2.什么是线程

一个进程要想执行任务,必须得有线程(每个进程至少要有一个线程), 一个进程的所有任务都在线程中执行。

阻塞线程的任务(网络加载)一般放在子线程,线程不能随便开,也会损耗性能

3.线程的串行

一个线程中任务的执行是串行的:那么只能一个一个按顺序执行这些任务。也就是说,在同一时间内,一个线程只能执行一个任务。

4.多线程:CPU不同调度

一个进程可以开启多条线程,每条线程可以并行执行不同的任务。例如:进程——》车间,线程——》车间工人

【多线程技术可以提高程序的执行效率,提高资源利用率】 比如可以同时开启三个线程分别下载三个文件(文件A、B、C)【并行、同时进行】

5.多线程的原理:同一时间,CPU只能从事处理1条线程,只有1条线程在工作。

多线程并发(同时)执行,其实就是CPU快速地在多条线程之间调度(切换)

如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

如果线程非常多,会发生什么情况

线程开销非常大,CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,每条线程被调度的频次会降低(线程的执行效率降低)。

6.多线程的优缺点

优点:能适当提高程序的执行效率,提高资源利用率。

缺点:A.线程是有开销的,创建线程大约需要90毫秒的创建时间。

            B.如果开启大量线程,会降低程序的性能。

            C.线程越多,CPU调度开销越大;

            D.程序设计更加复杂(线程通信、数据共享)

7.多线程在iOS开发中的应用

/*
主线程:一个iOS程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”。线程的主要作用:显示/刷新UI;处理UI事件(比如点击、滚动、拖拽事件等。)
使用注意:别将比较耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。
比如说:一个操作需要耗时10秒,但是用户在第5秒时点击了按钮就会很卡 5秒后才能执行。
*/

耗时操作的执行:将耗时操作放在子线程,当用户第五秒点击按钮那一刻就有反应,能同时处理耗时操作和UI控件的事件

iOS中多线程的实现方案:

/*
pthread   一套通用的多线程API;适用于Unix\Linux\Windows等系统;跨平台\可移植;使用难度大 C语言
NSThread 使用更加面向对象 ;简单易用,可直接操作线程对象   OC语言 手动管理 
GCD 旨在替代NSThread等线程技术;充分利用设备的多核  C语言  自动管理
NSOperation  基于GCD(底层是GCD)比GCD多了一些更简单实用的功能,使用更加面向对象 OC语言  自动管理
*/

 ①NSThread创建和启动线程

//一个NSThread对象就代表一条线程   
//创建、启动线程:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 线程一启动,就会在线程thread中执行self的run方法

主线程相关用法:

+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程
//number 1 主线程 ; != 1 其他线程
NSLog(@"%d == %@",i,[NSThread currentThread]);
//获得当前线程
NSThread *current = [NSThread currentThread];
//线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name; //项目开发中可以根据这个来查找崩溃原因
//线程的优先级
//current.threadPriority :优先级别 : 0.0 - 1.0; 默认0.5 只是保证CPU调度的可能性 数值越大,优先级越高,1.0最高.
//多线程目的:将耗时操作放在后台执行;建议:不要在开发过程中,修改优先级

其他创建线程的方式

//①创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
//②隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];//所有继承NSObject的类都可以调用
上述2种创建线程方式的优缺点 优点:简单快捷 缺点:无法对线程进行更详细的设置

控制线程的状态:NSThread 线程状态:阻塞,就绪,死亡

//①启动线程
- (void)start; //将线程加入到线程池里面-》CPU调度线程  运行状态由CPU控制
//②进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// ③进入阻塞状态  时间到了从阻塞到就绪状态
强制停止线程 
+ (void)exit;//④进入死亡状态 从线程池中拿出来,销毁   注意:一旦线程停止(死亡)了,就不能再次开启任务

 ②多线程的安全隐患资源共享

一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。(很容易引发数据错乱和数据安全问题)

安全隐患解决方法互斥锁@synchronized(锁对象🔐) 互斥锁:保证锁内的代码,同一时间只有1个线程执行

Thread A : 17 + 1 = 18;

Thread B : 17 + 1 = 18;

当Thread A 执行完+1操作后,就把线程锁住,ThreadB无法访问,等到Thread A 用完之后解锁,此时ThreadB才能访问。

互斥锁使用格式

@synchronized(锁对象) { // 需要锁定的代码  }
注意:锁定1份代码只用1把锁,用多把锁是无效的
//互斥锁的优缺点:
//优点:能有效防止因多线程抢夺资源造成的数据安全问题 
// 缺点:需要消耗大量的CPU资源
互斥锁的使用前提:多条线程抢夺同一块资源
//线程同步
//意思是:多条线程在同一条线上执行(按顺序地执行任务)互斥锁,就是使用了线程同步技术
@property (atomic, assign) int tickets;//票数
@property (nonatomic, strong) NSObject *lockObj ;
    self.lockObj = [NSObject new];
    self.tickets = 20;
    NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t1.name = @"售票员A";
    [t1 start];
    NSThread * t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t2.name = @"售票员B";
    [t2 start];
- (void)saleTickets {
    while (YES) {
        [NSThread sleepForTimeInterval:1.0];
        /*
         互斥锁:保证锁内的代码,同一时间只有1个线程执行;
         互斥锁的范围:尽量要小,范围大,效率就会差 只在核心代码里面加锁
         */
        // NSObject *lock = [NSObject new]; 不能用局部变量
        @synchronized (self.lockObj) {
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"剩下%d张票 %@",self.tickets,[NSThread currentThread]);
            }else
            {
                NSLog(@"卖完了,回家吧!");
                break;
            }
        }
    }
}

③原子性和非原子性

互斥锁:如果发现其他线程正在执行锁定的代码,线程就会进入休眠状态,等待线程完成,就唤醒

自旋锁:如果发现其他线程正在执行锁定的代码,线程就会死循环模式,等待线程完成,就执行锁定代码;

//原子性:1.保证属性的线程间安全(自旋锁)
//2.消耗性能:当下载文件时,如果没下载完成时,会把文件放在一个临时文件,当完全下载完成时才会加入后缀名。没下载成功不能打开
 如果不写atomic,多线程写入属性时,保证同一时间只有一个线程能够写入操作;。
//3.线程安全:多个线程操作数据,任然保持数据正确。主线程不考虑线程安全问题

//atomic:线程安全,需要消耗大量的资源
//nonatomic:非线程安全,适合内存小的移动设备
所有属性都声明为nonatomic
尽量避免多线程抢夺同一块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
@interface ViewController ()
@property(atomic,strong)NSObject *myAtomic;
@end
/*
 原子属性:
 如果不写atomic,多线程写入属性时,保证同一时间只有一个线程能够写入操作;
 单(线程)写,多(线程)读技术。
 线程安全:多个线程操作数据,仍然能保证数据正确 实际上,原子属性内部会有一把锁;自旋锁
 共同点:
 同一时间,只有线程访问;
 不同点:
 互斥锁:如果发现其他线程正在执行锁定的代码,线程就会进入休眠状态,等待线程完成,就唤醒
 自旋锁:如果发现其他线程正在执行锁定的代码,线程就会死循环模式,等待线程完成,就执行锁定代码;
 */
@implementation ViewController
//@synthesize 属性自动合成 创造一个带下划线前缀的实例变量名,同时使用这个属性生成getter 和 setter 方法。
@synthesize myAtomic = _myAomic;

-(NSObject *)myAtomic
{
    return _myAomic;
}

-(void)setMyAtomic:(NSObject *)myAtomic
{
    @synchronized (self) {
        _myAomic = myAtomic;
    }
}

④线程间通信

线程间通信:在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。

//线程间通信的体现
//1个线程传递数据给另1个线程; 在1个线程中执行完特定任务后,转到另1个线程继续执行任务

线程间通信常用方法

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
//①开启子线程:
NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(downImage) object:nil];[t1 start];
//②回到主线程:
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];//YES 相当于给Timer加了一个Runloop

 

 

 

 

 

 

posted @ 2018-07-05 13:09  淡然微笑_Steven  阅读(202)  评论(0编辑  收藏  举报