多线程

所谓多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

多线程的基本概念

  • 进程:可以理解为系统中正在运行的一个应用程序的实例,这个实例通过Process ID(进程ID,PID)进行唯一标识。同一个可执行文件可以并发地启动多个实例,但是每个实例的PID是不同的。
  • 线程:是现代操作系统操作的基本单元,线程只不过是一组寄存器的状态,一个进程可以存在多个线程。一个进程中的所有线程都共享虚拟内存空间、文件描述符和各种句柄。
  • 同步:在当前线程中执行任务,不具备开启线程的能力。
  • 异步:在新的线程中执行任务,具备开启新线程的能力。
  • 串行:执行任务时,一个任务的执行必须等到前一个的任务完成。
  • 并行:执行任务时,可以同时执行多个任务。
  • 主线程:在运行之后,会默认开启一条线程,称为“主线程”或“UI线程”,主线程是串行的。主线程的主要作用:显示/刷新UI界面,处理UI事件(比如点击事件、滚动事件、拖拽事件),这里需要注意的是:不要将耗时操作放在主线程。
  • 子线程:主线程以外的线程。子线程的作用:主要是执行耗时操作。

多线程的原理

我们知道,在同一时间内,CPU只能处理一条线程,即只有一条线程在执行。多线程的并发执行,就是CPU在多条线程之间实现快速的调度(切换),使得看起来就好像是一起在执行。

使用多线程可以适当提高程序的执行效率以及资源利用率(CPU、内存利用率),但是开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,就会占用大量的内存空间,降低程序的性能。随着线程数量越多,CPU在调度线程上的开销就越大。

线程的状态与生命周期

线程的生命周期要经过新建就绪运行阻塞死亡5种状态。当创建线程并启动以后,它并不是一启动就进入了运行状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,CPU需要在多条线程之间切换,所以线程状态会在运行、阻塞之间多次切换。
如图:

thread_life_cycle.png

多线程实现技术

方案 简介 生命周期 使用频率
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];
    }
}

你可能感兴趣的:(多线程)