何为并发 何为多线程
操作系统中正在运行的一个应用程序都会有一个独立的进程,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。一个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程),线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行。一个进程内可以有多个线程,这些线程作为操作系统调度的最小单元,负责执行各种各样的任务,这些线程都拥有各自的计数器、堆栈、局部变量等属性,并且可以访问共享内存,在一个线程内顺序的执行CUP无分叉的命令列。
当前常见的编程语言开发的程序,开发语言只是为了使程序员更好的操作,实质上,对计算机而言,程序会转换成其能理解的汇编语言进而解释成机器码来执行。机器码是按顺序执行的,一个复杂的多步操作只能一步步按顺序逐个执行。因此才有了多线程和多核CPU等技术的使用。
对于单核处理器,可以将多个步骤放到不同的线程,这样一来用户完成UI操作后其他后续任务在其他线程中,当CPU空闲时会继续执行,而此时对于用户而言可以继续进行其他操作。
对于多核处理器,如果用户在UI线程中完成某个操作之后,其他后续操作在别的线程中继续执行,用户同样可以继续进行其他UI操作,与此同时前一个操作的后续任务可以分散到多个空闲CPU中继续执行(当然具体调度顺序要根据程序设计而定),既解决了线程阻塞又提高了运行效率。苹果从双核A5处理器后又在A7中加入了协处理器,优化性能不只是在多线程,还有处理器的性能。
并发是一种现象,一种经常出现,无可避免的现象。它描述的是“多个任务同时发生,需要被处理”这一现象。它的侧重点在于“发生”。并行指的是一种技术,一个同时处理多个任务的技术。它描述了一种能够同时处理多个任务的能力,侧重点在于“运行”。并行的反义词就是串行,表示任务必须按顺序来,一个一个执行,前一个执行完了才能执行后一个。多线程,正是采用了并行技术,从而提高了执行效率。因为有多个线程,所以计算机的多个CPU可以同时工作,同时处理不同线程内的指令。创建多个线程,真正加快程序运行速度的,是并行技术。也就是让多个CPU同时工作。
在低层,GCD全局dispatch queue仅仅是工作线程池的抽象。这些队列中的Block一旦可用,就会被dispatch到工作线程中。提交至用户队列的Block最终也会通过全局队列进入相同的工作线程池(除非你的用户队列的目标是主线程,但是为了提高运行速度,我们绝不会这么干)。有两种途径来通过GCD“榨取”多核心系统的性能:将单一任务或者一组相关任务并发至全局队列中运算;将多个不相关的任务或者关联不紧密的任务并发至用户队列中运算。
线程小例
线程 start 后操作系统会给他分配相关的资源,包括单独的程序计数器和栈。操作系统会把这个线程作为一个独立的个体进行调度,分配时间片让它执行。线程被 CPU 调度后就会执行线程中的方法。
以NSThread为例:
@interface ViewController () @property (nonatomic, strong) NSThread *thread; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //创建线程 NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]; //设置线程的名称 [thread setName:@"线程A"]; self.thread = thread; } //当手指按下的时候,开启线程 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //开启线程 [self.thread start]; } -(void)test { //获取线程 NSThread *current=[NSThread currentThread]; NSLog(@"test---打印线程---%@",self.thread.name); NSLog(@"test---线程开始---%@",current.name); //设置线程阻塞,阻塞秒 NSLog(@"接下来,线程阻塞0.5秒"); [NSThread sleepForTimeInterval:.5]; //第二种设置线程阻塞,以当前时间为基准阻塞秒 NSLog(@"接下来,线程阻塞0.5秒"); NSDate *date=[NSDate dateWithTimeIntervalSinceNow:.5]; [NSThread sleepUntilDate:date]; for (int i= 0; i<10; i++) { NSLog(@"线程--%d--%@",i,current.name); if (9==i) { //结束线程 (线程死了不能复生,如果在线程死亡之后,再次点击屏幕尝试重新开启线程,则程序会挂) [NSThread exit]; } } NSLog(@"test---线程结束---%@",current.name); } @end
sleep方法使当前所在线程进入阻塞,只是让出 CPU ,并没有释放对象锁。由于休眠时间结束后不一定会立即被 CPU 调度,因此线程休眠的时间可能大于传入参数。
多线程中的内存析构
一个现代计算机通常由两个或者多个 CPU,每个 CPU 都包含一系列的寄存器,CPU 在寄存器上执行操作的速度远大于在主存上执行的速度。每个 CPU 有一个 CPU 缓存层。CPU 访问缓存层的速度快于访问主存的速度,但通常比访问内部寄存器的速度还要慢一点。
通常情况下,当一个 CPU 需要读取主存时,它会将主存的部分读到 CPU 缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。当 CPU 需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。
内存中分为堆和栈:堆为所有线程共享,存放运行时创建的对象和数组数据;栈为每个线程独有,栈中存放了当前方法的调用信息以及基本数据类型和引用类型的数据。
堆占用的内存由系统回收,堆中包含在程序中创建的所有对象,无论是哪一个线程创建的。一个对象的成员变量随着这个对象自身存放在堆上。不管这个成员变量是基本类型还是引用类型。静态成员变量跟随着类定义一起也存放在堆上。
栈在线程创建时创建,在一个方法中,你创建的局部变量和部分结果都会保存在栈中,并在方法调用和返回中起作用。当前栈只对当前线程可见。即使两个线程执行同样的代码,这两个线程仍然会在自己的线程栈中创建一份本地副本。因此,每个线程拥有每个本地变量的独有版本。栈中保存方法调用栈、基本类型的数据、以及对象的引用。
槽与坑
同一时间,一个CPU只能处理1条线程,只有1条线程在工作(执行)。多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)。如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。如果线程非常非常多,CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)。多个线程之间来回切换,意味着有多组栈和寄存器中的值需要不断地被备份、替换。把多个任务放在一个线程里,按顺序执行。只有一组寄存器和栈存在,显然效率更好一些,但也因此带来了线程阻塞。
有句老话说来着:“真正提高效率的是并行而不单单是多线程”。