说起多线程编程,大家应该都不陌生,从字面上理解,就是利用多线程技术编程。那么线程又是什么呢?今天我们就一起来探讨一下。
1.何为线程?
从程序执行的角度来说,1个CPU执行的CPU命令列为一条无分叉路径,称之为线程。
乍一看去可能有点绕。我们从操作系统的角度说起。在iOS或者MacOS中,当我们启动一个应用程序时,OS会首先将程序中的CPU指令配置到内存中。CPU从应用程序指定的地址开始,逐行执行CPU指令。
举例:在调用sum方法时,添加调试断点,运行。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self sumA:9 andB:2];
}
- (int)sumA:(int)a andB:(int)b
{
int c = a + b;
return c;
}
查看反汇编
0x105499823 <+51>: movq -0x8(%rbp), %rsi
0x105499827 <+55>: movq 0x4c12(%rip), %rdi ; "sumA:andB:"
0x10549982e <+62>: movq %rdi, -0x28(%rbp)
0x105499832 <+66>: movq %rsi, %rdi
0x105499835 <+69>: movq -0x28(%rbp), %rsi
0x105499839 <+73>: movl $0x9, %edx
0x10549983e <+78>: movl $0x2, %ecx
0x105499843 <+83>: callq *0x27c7(%rip) ; (void *)0x00007fff50b93780: objc_msgSend
0x105499849 <+89>: movl %eax, -0x2c(%rbp)
0x10549984c <+92>: addq $0x30, %rsp
0x105499850 <+96>: popq %rbp
0x105499851 <+97>: retq
此刻我们就很清楚的看到,程序先执行 地址0x105499823的命令movq,接着向后移动,执行下一条指令,即0x105499827的命令movq。就这样不断地执行下去。
有一种比较特殊的命令,比如if和for语句等控制语句或函数调用的情况下,执行命令行的地址会远离当前的位置即位置迁移。但是,由于一个CPU一次只能执行一条命令,不能同时执行某处分开的并列的两个命令,因此通过CPU执行的CPU指令就好比一条无分差的大道,其执行不会出现分歧。如图所示:
理解了上述过程,回过头来,再看我们开头提到线程的概念,相信大家应该已经理解了:线程就是一个CPU执行CPU指令的无分叉路径。
2.何为多线程编程?
现在一个物理的CPU芯片实际上有64个(64核)CPU,如果一个CPU核虚拟为两个CPU核工作,那么一台计算机上使用多个CPU核就是理所当然的事了。尽管如此,“1个CPU执行的CPU指令为一条无分叉路径”仍然不变。
这种无分叉的路径不止1条,存在多条时,即为”多线程“。在多线程中,1个CPU核执行多条不同路径上的不同命令。如图所示:
虽然CPU的相关技术有很多,其进步也令人眼花缭乱,但基本上1个CPU核一次能够执行的CPU命令始终为1。
那么怎样才能在多条路径中执行CPU指令呢?
在iOS和MacOS中,其内核XNU在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器的信息,继续执行切换路径的CPU指令。这称之为“上下文切换”。
由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像了”,而是真的提供了多个CPU核并行执行多个线程的技术。
这种利用多线程编程的技术就被成为“多线程编程”。
3.多线程编程的优缺点
了解了多线程编程以后,我们来看一下多线程编程的优缺点,首先看一下缺点,即存在什么安全隐患。
存在的安全隐患
1.数据竞争问题
多线程更新相同资源,会导致数据不一致。
这里有两个比较经典的案例,一个是银行存取钱的问题,一个是卖票系统,在后面我们会详细讲解。
2.死锁
停止等待事件的线程会导致多个线程相互持续等待。
3.当线程过多时,会消耗大量内存。
优势所在
尽管极易发生各种问题,也应当使用多线程编程。这是为什么呢?因为使用多线程编程,可以保证应用程序的响应性能。
应用程序在启动时,通过最先执行的线程,即“主线程”来描绘用户界面,处理触摸屏幕等事件。如果在该主线程中进行长时间处理,如访问数据库等耗时操作,就会阻塞主线程,妨碍主线程中RunLoop的执行,从而导致不能更新用户界面、应用程序的画面长时间停滞,就是我们平时所说的卡顿现象。
这就是需要将耗时操作放在其他线程,而非主线程操作的原因。如图所示:
将耗时操作放在其他线程中,需要回到主线程时,再回到主线程操作,就保证了用户操作界面的流畅度。
总结
1.何为线程:线程就是 1个CPU在执行CPU指令时的一条无分叉路径;
2.何为多线程编程:多线程编程就是利用多线程编程的技术o(╯□╰)o;
3.多线程编程的优缺点:
缺点:1.导致数据竞争; 2.死锁;3.线程过多时,会大量消耗内存;
优点:保证应用程序的响应性能,即良好的用户体验。
为了用户有更好的体验,所以我们还是要克服种种困难,来使用多线程编程。那么如何解决刚才提到的3个问题呢,且听下回分解。