小知识点好东西

线程管理

2017-05-24  本文已影响28人  Carden

术语

任务:准备执行的操作,通常就是一个block块

队列:queue,存放任务,管理任务

进程——苹果电脑里的活动监视器App里详细罗列了操作系统此时此刻正在运行的所有程序,同时标明了每一个进行的程序的所有线程。

线程——将一个程序都转换成汇编的CPU命令时,由一堆不分叉的Cpu指令组成的路径(主线程命令和分线程命令)

多线程编程——由多条不分叉的CPU指令所组成的路径就是多线程编程。优点:提高资源利用率和程序执行效率,用户体验 缺点:1、上下文切换频率太高会影响性能 2、资源竞争—>上锁(类似单例)3、死锁 4、开辟的线程其实类似于创建一个对象,会消耗大量内存(内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间)5、程序设计更加复杂:比如线程之间的通信、多线程的数据共享

CPU包括——1、物理CPU(硬件)2、CPU核 (一个物理CPU可以虚拟出多个CPU核,一个CPU核就相当于一个CPU的功能,可以分身,一个CPU核一个时刻只能执行一个CPU命令)

并发——虽然同一时间,CPU只能处理1条线程,只有1条线程在执行,但是CPU可以快速地在多条线程之间调度切换,如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。当然如果线程过多,会消耗大量的CPU资源,同时每条线程被调度执行的频次会降低(线程的执行效率降低)

多核就快

首先谈一谈就是程序执行的本质,我们写的代码都首先是转化成一行行的汇编代码才能被计算机所识别。这一行行的汇编代码其实也叫做CPU指令。这里面就涉及到CPU了,CPU就是读取这些指令的工具。当然,有一种汇编代码也就是CPU指令的结构是从头到尾不间断的,也就是这些CPU指令一点也没有分叉,从头到尾都是直路,就像高速公路一样,只有直路,没有岔路。可是程序的魅力就在于,遇到分岔路时或更复杂的十字路口时应该怎么办?肯定不能一根筋呀!于是CPU厂商就想到了一个方法,就是分身术,当遇到分叉路的时候就像孙悟空一样,复制一个自己来走新出现的分岔路。可是问题来了,如果第二次又遇到分叉路怎么办呢?两种思路,第一种再复制一个自己。第二种就是用那个复制的自己轮流前进。第一种方法主要受到CPU本身性能的局限。有的CPU只能复制一个自己,算上本身总共才两个。也有的CPU可以复制三个自己,算上本身总共有4个。这种具体去跑马路的车就叫做CPU核,所以你会发现,CPU越强大,也就是能够复制的自己越多,能够同时走的分叉路就越多,自然而然,四核就是比双核快。第二种方法就是让CPU的寄存器不断受到考验,当CPU分配那个复制的自己在那一条道路上奔跑时,必须时时记录下来这个复制的自己已经在每一条分叉路上已经跑了多远,并需要准确无误的保存在寄存器中,然后当CPU再一次将那个复制的自己放在相应地分岔路上继续跑时,就会读取寄存器里面的数据来继续前进。

主线程刷UI

因为分线程不能刷新UI,但是有的时候明明看见刷新UI的代码写在了分线程里,但这并不代表这些代码会被分线程(复制的CPU核)执行,分线程主要是负责数据的下载。(两种情况:1、下载完成后主动回到主线程,告诉主线程自己已经完成下载,可以让主线程执行分线程没有资格执行的代码 2、需要主线程不定期去主动检查也就是说,尽管你把刷新UI的代码写在了分线程,但分线程只会执行其中的下载代码,根本不会执行刷新UI的代码。刷新UI的代码会等到主线程不定期的主动检查分线程时才会被执行。所以这样会出现一些延时,所以应该在下载完成后主动回到主线程)

时间片

如果同一个CPU核去管理多个线程时,就会涉及到时间分配的问题,如果管理的是两个线程,线程一有10个命令,线程二有20个命令,CPU核会将时间片分配给两个线程,一一去执行完两个线程里面的所有命令,当一个线程切换到另一个线程时,系统会将当前线程的信息保存到寄存器中,等下次切换过来到时候唤醒寄存器,读取数据,从上一次的命令接着处理。这就是上下文切换。上下文切换——时间片分配(随机分配CPU核给那一个分线程,同时保存每一分线程上一次执行到的位置到寄存器中,告诉CPU核现在该从第几个CPU指令开始执行)

iOS多线程

NSThread

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 获得当前线程
NSThread *current = [NSThread currentThread];

// 线程名字
- (void)setName:(NSString *)name;
- (NSString *)name;

// 获得主线程
+ (NSThread *)mainThread; 

// 判断是否主线程
- (BOOL)isMainThread; 
+ (BOOL)isMainThread;

// 创建线程后自动启动
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

// 隐式创建并启动线程
[self performSelectorInBackground:@selector(run)  withObject:nil];

// 阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 强制停止线程
+ (void)exit;

GCD全称Grand Central Dispatch

纯C语言函数,为多核并行运算而生,自动利用更多的CPU内核(比如双核、四核),且自动管理线程的生命周期(创建线程、调度任务、销毁线程),只需告诉GCD想要执行的任务,根本无需写任何管理线程生命周期的代码。队列管理任务执行顺序,同步异步决定任务执行线程。

// 创建并发队列(队列名称,队列类型)
dispatch_queue_t   myQueue = dispatch_queue_create(const char *label,   dispatch_queue_attr_t attr);

// 获得默认并发队列(队列优先级,缺省参数0)
dispatch_queue_t  systemQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

// 任务加入并发队列
dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
});
// 创建串行队列(队列名称,队列类型)
dispatch_queue_t  myQueue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);

// 直接获取主队列 = 特殊串行队列
dispatch_queue_t  systemQueue = dispatch_get_main_queue();

// 任务加入串行队列
dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
});
// 继续当前线程(队列,任务)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 开启新线程(队列,任务)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 从子线程回到主线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // 执行耗时的异步操作...


        dispatch_async(dispatch_get_main_queue(), ^{
               // 回到主线程,执行UI刷新操作

         });
});

// 对比performSelector的线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
// GCD延时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...
});

// 对比performSelector延时
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

// 对比NSTimer延时
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
// 保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});


// 创建Timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置定时器的触发时间(1秒后)和时间间隔(每隔2秒)
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), 2 * NSEC_PER_SEC, 0);
// 设置Block回调
dispatch_source_set_event_handler(self.timer, ^{
     NSLog(@"Timer %@", [NSThread currentThread]);
});
// 开启定时器
dispatch_resume(self.timer);


// 取消置空定时器
dispatch_cancel(self.timer);
self.timer = nil;
// 队列组管理任务一和任务二
dispatch_group_t group =  dispatch_group_create();

// 异步执行任务一
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
});

// 异步执行任务二
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   
});

// 任务一和任务二都结束Block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
});
/**
 *   GCD快速异步遍历
 */
- (void)apply
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSString *from = @"初始路径";
    NSString *to = @目标路径";
    
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSArray *subpaths = [mgr subpathsAtPath:from];
    
    dispatch_apply(subpaths.count, queue, ^(size_t index) {
        NSString *subpath = subpaths[index];
        NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
        NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
        [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; // 剪切
        NSLog(@"%@---%@", [NSThread currentThread], subpath);
    });
}

/**
 *  对比传统异步遍历
 */
- (void)moveFile {
    NSString *from = @"初始路径";
    NSString *to = @目标路径";

    NSFileManager *mgr = [NSFileManager defaultManager];
    NSArray *subpaths = [mgr subpathsAtPath:from];

    for (NSString *subpath in subpaths) {
        NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
        NSString *toFullpath = [to stringByAppendingPathComponent:subpath];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; // 剪切
        });
    }
}

NSOperation&&NSOperationQueue

NSOperation是个并不具备封装操作能力的抽象类,必须使用它的子类来封装操作,NSOperation子类包括NSInvocationOperation、NSBlockOperation、自定义子类继承NSOperation。如果仅仅是实例化了一个操作对象,然后简简单单的执行这个操作的start方法,这样没有丝毫意义,还不如在.m文件里面写一个私有方法来得直接,可是一旦把实例化的NSOperation对象放到NSOperationQueue队列之中,添加到队列里面的操作对象会自动开始调用操作对象的start方法,开始异步在一条新的线程里面执行封装在操作对象里面的一系列代码,这就实现了分线程操作了,好神奇有木有!

//  创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

//  默认同步,除非添加操作到队列
- (void)addOperation:(NSOperation *)op;
//  创建NSBlockOperation
+ (id)blockOperationWithBlock:(void (^)(void))block;

//  添加更多操作(只要任务数>1,就会异步执行操作)
- (void)addExecutionBlock:(void (^)(void))block;


重写父类NSOperation的main方法,在main方法里面写入我想要封装的执行的任务,但是有一点需要特别注意,就是自己写的这段代码很可能会是在子线程里面异步执行,既然是异步操作,自然无法访问主线程的自动释放池,对象的内存释放便是需要重点考虑的内容。经常的做法就是,在封装想要执行的代码之前,必须先判断一下这个操作对象是否已经被取消,因为一旦开始执行进入了子线程就无法在判断isCancelled这个属性了,所以必须在开始执行封装的代码之前,必须先判断这个操作对象是否已经被取消。如果判断已经取消,我需要做的就是一些内存的管理。
// 一定先让操作A执行完毕,然后才能执行操作B
[operationB addDependency:operationA]; 
!!!一定注意不能相互依赖
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
//  取消队列操作
- (void)cancelAllOperations;
- (void)cancel;

// 暂停Or恢复队列
- (void)setSuspended:(BOOL)bool; 
- (BOOL)isSuspended;

GCD和NSOperation的区别?

GCD无法控制线程的最大并发数,而NSOperation可以控制同时进行的线程个数!

开辟多线程?

作为NSObject的类别,因此只要是控制器的对象就可以直接调用这个方法。而且不仅仅可以通过performSelectorInBackground将耗时操作放进分线程里,更可以通过performSelectorOnMainThread将分线程里的参数传递到主线程以便进一步操作,例如刷新UI

thread本身就是线的意思。通过NSThread类实例化一个对象。然后直接通过初始化方法就可以像为UIButton增添点击事件那样增添一个在分线程里进行的方法。而且可以传递给这个分线程方法一个无论什么对象类型的参数。但是特别注意:通过NSThread类实例化的对象所初始化增添的分线程方法必须通过[对象 start]方法表示开始执行分线程方法。而且无论第三方工具如何丰富,本质上都是对NSThread进行一系列操作。所以说在任何情况下都可以调用[NSThread currentThread]这个类方法获取到当前的线程信息,有点像代码版的活动监视器哈!这是苹果提供的三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁的问题,这会导致一定得性能开销。

伟大的中枢调度器,充分发挥CPU内核的性能,自动管理线程的生命周期,包括创建线程,调度任务和销毁线程。而且使用过程中只需添加任务即可,无需管理线程。

一块资源可能会被多个线程共享,通常添加互斥锁@synchronized(self){}来避免引发数据错乱和数据安全问题。但是添加互斥锁会影响手机的性能,所以尽量将加锁和资源抢夺业务的逻辑交给服务器端处理,线程不会孤立存在,子线程下载图片,在主线程刷新UI显示图片。使用@synchronized(self的锁对象){}包起来的代码同一时刻只能被一个线程执行,而且加锁的时候尽量缩小范围,范围越大就越消耗性能。

串行队列Vs并行队列?

相同点

不同点

互斥锁

@synchronized(锁对象) { // 需要锁定的代码  }

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

+ (instancetype)sharedInstance
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}

优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
前提:多条线程抢夺同一块资源
线程同步:多条线程在同一条线上执行(按顺序地执行任务)

atomic:原子属性,为setter方法加锁(默认就是atomic),线程安全,需要消耗大量的资源
nonatomic:非原子属性,不会为setter方法加锁,非线程安全,适合内存小的移动设备,尽量避免多线程抢夺同一块资源,尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力


上一篇 下一篇

猜你喜欢

热点阅读