进程、线程
进程
当一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定独立功能。
线程
线程是进程中的一个执行单元,负责当前进程中程序的之心,一个进程至少有一个线程。一个进程中可以有多个线程。
单线程程序:若有多个任务只能一次执行
多线程程序:若有多个任务,可以同时执行
对于CPU单一个核心而言,某个时刻只能执行一个线程,而CPU在多个线程之间切换的速度相对我们的感觉要快,看上去就是在同一时刻运行。
多线程并不能提高程序的运行速度,但能提高运行效率
任务
任务:执行操作的意思,就是要在线程中执行的代码
同步执行(sync)
同步添加任务到队列中,队列在任务结束之前会一直等待,直到任务完成之后再继续执行
只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async)
异步添加任务到队列中,队列不会等待,可以继续执行其他任务。
可以在新的线程中执行任务,具备开启线程的能力,但不一定开启新线程。
队列
队列(dispatch queue)
执行任务的等待队列,即用来存在任务的队列。队列是一种特殊的线性表,采用FIFO(first in first out)的原则。新的任务总是被插到队列的末尾,读取任务总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
串行队列(serial dispatch queue)
只开启一个线程,每次只能执行一个任务,一个任务执行完毕后才能执行下一个任务。
并发队列(concurrent dispatch queue)
可以让多个任务并发(同时)执行,可以开启多个线程,并同时执行任务。并行队列的并发功能只能在异步下才有效。
主队列
是一个特殊的串行队列,添加到主队列的任务只能在主线程中执行
![](https://img.haomeiwen.com/i4247320/63a1900e0a2c0260.png)
iOS多线程方案
![](https://img.haomeiwen.com/i4247320/467ea878bd3b53d3.png)
队列 组Group 的使用
异步并发执行任务1、任务2,等任务1、任务2都执行完毕后,再回到主线程执行任务3
// 创建调度组
dispatch_group_t group = dispatch_group_create();
//创建并发队列
dispatch_queue_t que = dispatch_queue_create("qye", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, que, ^{
NSLog(@"任务1");
});
dispatch_group_async(group, que, ^{
NSLog(@"任务2");
});
dispatch_group_notify(group, que, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务3");
});
});
栅栏函数 dispatch_barrier_async
这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
栅栏函数是GCD提供的用于阻塞分割任务的一组函数。就像其定义一样,其主要作用就是在队列中设置栅栏,来人为干预队列中任务的执行顺序.常用的栅栏函数有两个##dispatch_barrier_async和dispatch_barrier_sync.
dispatch_barrier_async:用于提交异步执行栅栏函数块任务,并立即返回.所以该函数不会阻塞线程,只会阻塞任务执行。栅栏函数提交之后并不会立即执行,而是会直接返回。等待在栅栏函数之前提交的任务都执行完成之后,栅栏函数任务会自动执行,在栅栏函数之后提交的任务必须在栅栏函数执行完成之后才会执行.
dispatch_barrier_sync:用于提交同步执行栅栏函数块任务,并不会立即返回,需要等待栅栏函数提交的任务执行完毕之后才会返回。与dispatch_barrier_async不同,由于该函数需要同步执行,在该栅栏函数任务执行完成之前,函数不会返回,所以此时后边的任务都会被阻塞.
栅栏函数可以干嘛?
假设我们有三个任务task 001-003,要求task 001 task 002顺序不定,task 003 需要在task 001 和 task 002执行完成之后才可以执行,使用dispatch_barrier_async
dispatch_queue_t queue = dispatch_queue_create("com.queueDemo", 0);
dispatch_async(queue, ^{
NSLog(@"task 001 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task 002 --- %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"task 003 --- %@", [NSThread currentThread]);
});
NSLog(@"task 004 --- %@", [NSThread currentThread]);
输出结果为:
task 004 --- <NSThread: 0x282c34cc0>{number = 1, name = main}
task 001 --- <NSThread: 0x282c6c540>{number = 3, name = (null)}
task 002 --- <NSThread: 0x282c6c540>{number = 3, name = (null)}
task 003 --- <NSThread: 0x282c6c540>{number = 3, name = (null)}
如果使用dispatch_barrier_sync
dispatch_queue_t queue = dispatch_queue_create("com.queueDemo", 0);
dispatch_async(queue, ^{
NSLog(@"task 001 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task 002 --- %@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"task 003 --- %@", [NSThread currentThread]);
});
NSLog(@"task 004 --- %@", [NSThread currentThread]);
输出结果:(task 001 task 002随机, task 003在任务task 001 task 002执行完成之后才能执行)
task 001 --- <NSThread: 0x280a132c0>{number = 3, name = (null)}
task 002 --- <NSThread: 0x280a132c0>{number = 3, name = (null)}
task 003 --- <NSThread: 0x280a46e80>{number = 1, name = main}
task 004 --- <NSThread: 0x280a46e80>{number = 1, name = main}
使用栅栏函数需要注意什么?
跟所有的串行队列一样,向串行队列中添加任务,要防止在串行队类中堵塞线程导致的死锁.例如
dispatch_queue_t queue = dispatch_queue_create("com.supportHongKongPolice", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
dispatch_async(queue, ^{
NSLog(@"task 001");
});
dispatch_barrier_sync(queue, ^{
NSLog(@"task 002");
});
NSLog(@"task 003");
});
信号量dispatch_semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
// 信号量初始值
int vaue = 1;
//创建信号量
dispatch_semaphore_t sema = dispatch_semaphore_create(vaue);
/*
这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,
且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
*/
//dispatch_semaphore_wait 等待信号量
// long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
// 这个函数会使传入的信号量dsema的值减1,如果传入信号量的值等于0,函数将持续等待不返回
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
/*
dispatch_semaphore_signal 发送信号量
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
这个函数会使传入的信号量dsema的值加1
*/
dispatch_semaphore_signal(sema);
多线程安全
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
解决方案:使用线程同步技术,如:加锁
OSSpinLock
OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
目前已经不再安全,从iOS10开始不支持,可能会出现优先级反转问题。如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
需要导入头文件#import <libkern/OSAtomic.h>
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 尝试加锁,需要等待就不加锁,直接返回FALSE,不需要等待就加锁,返回true
bool res = OSSpinLockTry(&lock);
// 加锁
OSSpinLockLock(&lock);
// 解锁
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
需要导入头文件#import <os/lock.h>
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 尝试加锁
os_unfair_lock_trylock(&lock)
// 加锁
os_unfair_lock_lock(&lock);
// 解锁
os_unfair_lock_unlock(&lock);
pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
需要导入头文件#import <pthread.h>
pthread_mutex 锁类型
1.#define PTHREAD_MUTEX_NORMAL 0
2.#define PTHREAD_MUTEX_ERRORCHECK 1
3.#define PTHREAD_MUTEX_RECURSIVE 2 递归锁
4.#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
普通锁
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
// 尝试加锁
pthread_mutex_trylock(&mutex);
// 加锁
pthread_mutex_lock(&mutex);
// 解锁
pthread_mutex_unlock(&mutex);
// 销毁相关资源
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
递归锁
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
条件锁。等待进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁
// 初始化锁
pthread_mutex_t mutex;
// NULL代表使用默认属性
pthread_mutex_init(&mutex, NULL);
// 初始化条件
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
// 等待条件(进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁)
pthread_cond_wait(&cond, &mutex);
// 激活一个等待该条件的线程
pthread_cond_signal(&cond);
// 激活所有等待该条件的线程
pthread_cond_broadcast(&cond);
// 销毁资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
NSLock
NSLock是对mutex普通锁的封装,面向对象
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSRecursiveLock
NSRecursiveLock是对mutex递归锁的封装,API与NSLock基本一致
NSCondition
NSCondition 是对 mutex条件锁的封装
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
@synchronized
@synchronized是对mutex递归锁的封装
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
@synchronized(obj) { // objc_sync_enter
}
线程同步方案对比
性能方面,性能从高到低排序
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
如何选择?
开发方便考虑 GCD信号量,串行线程。
面向对象考虑 NSLock,NSRecursiveLock,NSCondition,NSConditionLock
追求性能考虑 os_unfair_lock
代码简单考虑 @synchronized
自旋锁互斥锁对比
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
iOS读写安全方案
读写安全,需要多读单写。常见的方案有:
pthread_rwlock:读写锁
dispatch_barrier_async:异步栅栏调用
pthread_rwlock
等待的线程会进入休眠
pthread_rwlock_t lock;
// 初始化锁
pthread_rwlock_init(&lock, NULL);
// 读 加锁
pthread_rwlock_rdlock(&lock);
// 读 尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写 加锁
pthread_rwlock_wrlock(&lock);
// 写 尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 销毁
pthread_rwlock_destroy(&_lock);
dispatch_barrier_async
这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
// 读
dispatch_async(queue, ^{
});
// 写
dispatch_barrier_async(queue, ^{
});
网友评论