线程是进程中的一条执行路径,在一个线程中任务的执行是串行的,同一时间内,一个线程只能执行一个任务
多线程
- 在多线程开发中, 耗时操作我们一般放在子线程。耗时操作会卡住主线程,严重影响UI流畅度。
- 一个进程中可以开启多条线程,多条线程可以并发(同时)执行不同的任务。
多线程特点
- 同一时间,CPU(单核)只能处理1条线程,只有一条线程在工作(执行)。
- 多线程并发(同时)执行,其实是CPU(单核)快速在多条线程之间调度(切换)。
- 多核CPU是真正意义上的多线程。
- 如果线程太多, CPU在多个线程之间调度,会消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程执行效率会降低)。
多线程的优点:
能适当提高程序的执行效率。
缺点:
- 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB),大约需要90毫秒的创建时间。
- 开启大量的线程,会降低程序的性能,CPU在调度线程上的开销很大。
- 程序设计更加复杂:比如线程之间的通信、多线程的数据共享。
主线程
- 一个iOS程序运行后,默认开启一个线程,称为主线程或UI线程。
- 显示、刷新UI界面。
- 处理UI事件(点击、滚动、拖拽事件等)。
1, NSThread 方法简单介绍
初始化NSThread对象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
或者
NSThread *thread = [[NSThread alloc] initWithBlock:^{
}];
执行任务
[thread start];
把任务执行在分离新线程中
[NSThread detachNewThreadSelector:@selector(demo2:) toTarget:self withObject:nil];
或者
[NSThread detachNewThreadWithBlock:^{
}];
在后台线程执行任务
[self performSelectorInBackground:@selector(demo3:) withObject:@"backgrond"];
回到主线程执行任务
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
线程的状态
- 创建线程之后,系统会把线程放在可调度线程池中,当调用 start 之后,线程就变成 Runnable (就绪)状态。
- 之后 CPU 调度当前线程运行, 之后再切换到其他线程。
- 通多sleep 方法或者等待同步锁,可以使线程处于阻塞状态, 阻塞的线程会移出可调度线程池,停止参与调度。
- 等 sleep 时间到或者等待同步锁打开,会被重新放在x可调度线程池中,继续参与调度(执行任务)。
- 当线程执行完毕或者强制退出,线程处于死亡状态。
// 1 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sleepStatus) object:nil];
// 2,线程就绪状态
[thread start];
- (void)sleepStatus {
// 3, 阻塞-sleep,当运行满足某个条件,会让线程睡
// 提示: sleep 是类方法,会直接休眠当前线程
NSLog(@"睡一会");
[NSThread sleepForTimeInterval:2];
for (int i = 0; i < 20; i++) {
NSLog(@"%d, %@", i, [NSThread currentThread]);
// 4, exit -- 强行终止: 类方法,强制停止当前线程, 一旦强行终止,后续的代码都不会执行
// exit -- 会杀掉主线程,但是 APP 不会崩溃
if (i == 12) {
[NSThread exit];
}
}
}
线程安全问题
问题: 当剩余 20 张火车票,两台机器同时售卖,A 机器读取剩余 20, 卖一张还剩 19, B机器读取剩余 20,卖一张还剩 19(相当于两条线程同时访问同一块内存)
测试代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
thread1.name = @"线程 A";
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
thread2.name = @"线程 B";
[thread2 start];
}
- (void)threadDemo {
while (YES) {
[NSThread sleepForTimeInterval:1.0];
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩下: %ld 张票\n%@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"票已经卖完了!");
break;
}
}
}
打印结果:
2019-07-02 15:52:18.353072+0800 005--NSThread资源共享[1766:741375] 剩下: 19 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:18.353080+0800 005--NSThread资源共享[1766:741376] 剩下: 18 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:19.358301+0800 005--NSThread资源共享[1766:741375] 剩下: 17 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:19.358352+0800 005--NSThread资源共享[1766:741376] 剩下: 16 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:20.360759+0800 005--NSThread资源共享[1766:741376] 剩下: 15 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:20.363214+0800 005--NSThread资源共享[1766:741375] 剩下: 14 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:21.361613+0800 005--NSThread资源共享[1766:741376] 剩下: 13 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:21.367609+0800 005--NSThread资源共享[1766:741375] 剩下: 12 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:22.367059+0800 005--NSThread资源共享[1766:741376] 剩下: 11 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:22.372847+0800 005--NSThread资源共享[1766:741375] 剩下: 10 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:23.372406+0800 005--NSThread资源共享[1766:741376] 剩下: 9 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:23.378088+0800 005--NSThread资源共享[1766:741375] 剩下: 8 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:24.377758+0800 005--NSThread资源共享[1766:741376] 剩下: 7 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:24.380684+0800 005--NSThread资源共享[1766:741375] 剩下: 6 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:25.383125+0800 005--NSThread资源共享[1766:741376] 剩下: 5 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:25.384562+0800 005--NSThread资源共享[1766:741375] 剩下: 4 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:26.388494+0800 005--NSThread资源共享[1766:741376] 剩下: 3 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:26.388533+0800 005--NSThread资源共享[1766:741375] 剩下: 2 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:27.393722+0800 005--NSThread资源共享[1766:741376] 剩下: 1 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:27.393722+0800 005--NSThread资源共享[1766:741375] 剩下: 1 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:28.398984+0800 005--NSThread资源共享[1766:741375] 剩下: 0 张票
{number = 4, name = 线程 A}
2019-07-02 15:52:28.398984+0800 005--NSThread资源共享[1766:741376] 剩下: 0 张票
{number = 5, name = 线程 B}
2019-07-02 15:52:29.400585+0800 005--NSThread资源共享[1766:741375] 票已经卖完了!
2019-07-02 15:52:29.404393+0800 005--NSThread资源共享[1766:741376] 票已经卖完了!
打印结果的最后几行出现剩余相同张数的情况(每次执行结果都可能不一样,在多线程的开发中不要相信一次的运行结果)。
通过添加互斥锁解决问题:
把改成
// 互斥锁 -- 保证锁内的代码,同一时间只有一条线程执行
// 互斥锁的范围,应该尽量小,范围大了,效率就低
@synchronized (self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩下: %ld 张票\n%@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"票已经卖完了!");
break;
}
}
多线程卖票的问题就解决了。自己动手试试吧!!!