C语言中的pthread
和OC中的NSThread
有什么联系?如何使用呢?
一、pthread
在OC中使用C语言的线程,必须导入C语言中的线程库pthread.h
。
1 | // 1. 导入C语言线程库 |
通过上述代码执行耗时任务时,不会阻塞主线程。
二、NSThread
2.1. 创建线程
使用NSThread
创建子线程有多种方法。
第一种:通过实例方法初始化
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
第二种:分离一条新的子线程(类方法,自动开启任务)
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
第三种:开启后台线程(自动开启任务)
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
只有第一种创建线程的方法是有线程实例对象的,其他方式都没有返回实例。也就意味着,平时使用实例初始化线程更加易于控制。
2.2. 线程属性和生命周期
- 为线程添加
name
更容易区分线程。
1 | thread.name = @"线程01"; |
- 设置线程优先级,范围是0.0~1.0,默认是0.5,1.0优先级最高。优先级越高线程被CPU调度的概率就越大(注意是概率)。
1 | thread.threadPriority = 1.0; |
- 当线程的内部任务执行完毕之后,线程对象就会被自动释放。
2.3. 线程状态
启动线程:(首先进入就绪状态,然后到运行状态。当线程任务执行完自动进入死亡状态)
1
- (void)start;
阻塞(暂停)线程:(进入阻塞状态)
1
2+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;强制停止线程
1
+ (void)exit;
示例代码:
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
通过控制台可以看到任务开始和任务结束之间有5s间隔,间隔就是线程正处在阻塞状态。
三、线程安全
一块资源可能会被多个线程共享,也就是说多个线程可能会访问同一块资源,当多个线程访问同一块资源时(资源可以是对象、变量、文件等),很容易引发数据错乱和数据安全问题。
示例代码(售票):
1 | - (void)viewDidLoad { |
上面代码看似没有问题,但是当我们把卖票时间拉长(任务执行时间大于线程调度时间)时,就会发现问题。
1 | - (void)saleTicket { |
发现有重复卖票的行为。可以通过**互斥锁(同步锁)**解决线程安全问题。当线程A访问(读)资源时,对资源进行加锁,访问(写)结束后,再把锁打开。如果线程B在线程A访问资源时也想访问这块资源,由于锁的存在,线程B只能等待资源没有加锁时才能访问。
1 | /* |
思考:为什么锁对象要使用全局变量?
如果线程每次访问资源时都要加一把新锁,就会导致加的锁是无效的。
1 | - (void)saleTicket { |
如果把互斥锁加到while
的外层,会怎样呢?结果就是一个售票员把所有的票都卖完了。所以不能在随意位置进行加锁,只有当多个线程会抢夺同一块资源时,对该资源进行加锁,否则随意加锁不仅没有效果,还会损耗程序性能。
互斥锁的本质是使用了线程同步技术(多条线程在同一条线上顺序地执行任务)。
四、原子和非原子属性
OC在定义属性时有nonatomic
和atomic
两种选择。
atomic
:原子属性,线程安全,为setter
方法加锁(默认就是atomic
)nonatomic
:非原子属性,非线程安全,不会为setter
方法加锁
既然atomic
是线程安全的,为什么开发中会经常使用nonatomic
?
主要有两点因素:
nonatomic
性能好;- 开发中多线程抢夺资源的情况并不多。
五、线程通信
在1个进程中,一般情况下线程不是单独存在的,多个线程之间需要经常进行通信。
线程通信特点:
- 1个线程传递数据给另1个线程
- 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
线程通信常用方法:
1 | /* |
下载图片是一个耗时操作,尤其当图片比较大的时候更加明显。
1 | - (void)showImage { |
为避免多图下载时,不影响主线程任务执行(卡屏)。可以将下载操作放到子线程,当下载完成后通知主线程刷新UI。
1 | - (void)loadImage { |