所谓多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
多线程的基本概念
- 进程:可以理解为系统中正在运行的一个应用程序。
- 线程:一个进程想要执行任务,至少需要一个线程。线程是进程的基本执行单元,进程中所有任务的的执行必须在线程中。
- 线程串行:线程的执行顺序是串行的,一个任务的执行必须等到前一个的任务完成。
- 线程并行:一个进程中可以有多条线程,每条线程可以并行(同时)执行不同的任务。
- 多线程的原理:同一时间,CPU只能处理一条线程,只有一条线程在执行,多线程的并发执行,就是CPU在多条线程之间实现快速的调度(切换),使得看起来就好像是一起在执行
- 主线程:在运行之后,会默认开启一条线程,称为“主线程”或“UI线程”,主线程是串行的。主线程的主要作用:显示/刷新UI界面,处理UI事件(比如点击事件、滚动事件、拖拽事件),这里需要注意的是:不要将耗时操作放在主线程。
- 子线程:主线程以外的线程。子线程的作用:主要是执行耗时操作。
多线程的原理
我们知道,在同一时间内,CPU只能处理一条线程,即只有一条线程在执行。多线程的并发执行,就是CPU在多条线程之间实现快速的调度(切换),使得看起来就好像是一起在执行。
使用多线程可以适当提高程序的执行效率以及资源利用率(CPU、内存利用率),但是开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,就会占用大量的内存空间,降低程序的性能。随着线程数量越多,CPU在调度线程上的开销就越大。
线程的状态与生命周期
线程的生命周期要经过新建、就绪、运行、阻塞和死亡5种状态。当创建线程并启动以后,它并不是一启动就进入了运行状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,CPU需要在多条线程之间切换,所以线程状态会在运行、阻塞之间多次切换。
如图:
多线程实现技术
方案 | 简介 | 生命周期 | 使用频率 |
---|---|---|---|
pthread | 纯C语言,跨平台 | 程序员管理 | 几乎不用 |
NSThread | OC语言,使用面向对象的思维,直接操纵线程对象 | 程序员管理 | 偶尔使用 |
GCD | 纯C语言,用于取代NSThread,可用充分利用设备的多核 | 自动管理 | 经常使用 |
NSOperation | 基于GCD,更面向对象 | 自动管理 | 经常使用 |
多线程安全隐患
资源共享问题:一块资源有可能会被多个线程共享,即多个线程同时访问一个资源。想要解决资源的话,就需要加锁。加锁可以保证当一个线程正在对一块资源进行写操作的时候,这时候是不允许其他的线程对该资源进行访问,只有当该线程访问结束后,其他线程才能按顺序进行访问。
以卖票问题为例,代码如下:
@interface Multithread()
@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;
@property (nonatomic, strong) NSThread *thread3;
@property (nonatomic, assign) NSInteger ticketCount;
@end
@implementation Multithread
- (instancetype)init {
if (self = [super init]) {
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(_sellTickets) object:nil];
self.thread1.name = @"thread1";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(_sellTickets) object:nil];
self.thread2.name = @"thread2";
self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(_sellTickets) object:nil];
self.thread3.name = @"thread3";
self.ticketCount = 100;
}
return self;
}
- (void)begin {
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
- (void)_sellTickets {
while (self.ticketCount > 0) {
[NSThread sleepForTimeInterval:1];
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"%@买了一张票,还有%ld", [NSThread currentThread].name, self.ticketCount);
}
}
}
@end
打印出来的结果:
1.jpg从上面可以看出打印的余票数据出现错误。
解决方法:
1.互斥锁(同步锁):
- (void)_sellTickets {
while (self.ticketCount > 0) {
@synchronized(self) {
[NSThread sleepForTimeInterval:1];
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"%@买了一张票,还有%ld", [NSThread currentThread].name, self.ticketCount);
}
}
}
}
当有线程在执行代码的时候,执行体也就是代码块就会被加锁,等这个线程处理完成后,下一个线程才能使用,相当于将并行改成了串行,另外互斥锁在保护单例的时候可以使用。它的缺点就是消耗性能。
2.NSLock
- (void)_sellTickets {
while (self.ticketCount > 0) {
[self.lock lock];
[NSThread sleepForTimeInterval:1];
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"%@买了一张票,还有%ld", [NSThread currentThread].name, self.ticketCount);
}
[self.lock unlock];
}
}
网友评论