近期要写一个多线程工具,把之前学习的多线程,以及线程同步复习一下
多线程的本质:有多条线程,但是只能执行一条,如果间隔时间设置的足够小,就给人的感觉是多条线程是同时进行的,时间片轮转调度算法
iOS中线程同步方案
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- Dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
OSSpinLock - 自旋锁
等待锁🔐的线程会处于忙等状态,一直占用CPU资源
会出现优先级翻转的问题,如果线程之间的优先级不同,如果低优先级的锁先进来,把锁锁住,那么高优先级的线程进来就会一直忙等,但是系统又会分配时间资源给线程高的,从而导致低优先级的线程无法执行完自己的代码,从而导致优先级低的锁无法释放。
/// 初始化锁
self.lock = OS_SPINLOCK_INIT;
- (void)saleTicket{
/// 加锁
OSSpinLockLock(&_lock);
//BOOL isLock = OSSpinLockTry(&_lock);
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩下%d张票 = %@",self.ticketsCount,[NSThread currentThread]);
/// 解锁
OSSpinLockUnlock(&_lock);
}
os_unfair_lock - iOS10后支持 (本质是互斥锁)
用于替代OSSpinLock 的锁,使用的技术不再是忙等,而是休眠等待唤醒
/// 初始化锁
self.lock = OS_UNFAIR_LOCK_INIT;
- (void)saleTicket{
/// 加锁
os_unfair_lock_lock(&_lock);
// BOOL isLock = os_unfair_lock_trylock(&lock);
// 卖票
[self sale];
/// 解锁
os_unfair_lock_unlock(&_lock);
}
pthread_mutex
mutex 叫做"互斥锁",等待锁的线程会处于休眠状态
/// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
/// 初始化锁
pthread_mutex_init(&_lock, &attr);
//销毁属性 pthread_mutexattr_destroy(&attr);
//销毁锁 pthread_mutex_destroy(&_lock);
- (void)saleTicket{
/// 加锁
pthread_mutex_lock(&_lock);
///尝试 加锁
//BOOL islock = pthread_mutex_trylock(&_lock);
// 卖票
[self sale];
/// 解锁
pthread_mutex_unlock(&_lock);
}
/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL 0 //普通状态锁
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 //用于处理递归锁
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
递归锁
递归锁:允许同一个线程对一把锁重复加锁
线程一 : 调用saleTicket (+🔐)
-
调用
saleTicket(+🔐)- 调用
saleTicket(+🔐)
- 调用
线程二:调用saleTicket (发现已经被加锁了,等待)
当锁中间的代码遇到递归调用,打印的结果永远只有一条saleTicket,因为没有人能够走到解锁的那一步。
- (void)saleTicket{
/// 加锁
pthread_mutex_lock(&_lock);
///尝试 加锁
//BOOL islock = pthread_mutex_trylock(&_lock);
NSLog(@"%s",__func__);
// 卖票 递归调用
[self saleTicket];
/// 解锁
pthread_mutex_unlock(&_lock);
}
pthread是支持递归锁的,只需要把初始化属性改为递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
条件锁
@property (nonatomic, assign) pthread_cond_t cond;
/// 初始化条件
pthread_mutex_init(&_lock, &attr);
// 销毁条件
pthread_cond_destroy(&_cond);
[[[NSThread alloc]initWithTarget:self selector:@selector(__remove) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(__add) object:nil]start];
- (void)__remove{
pthread_mutex_lock(&_lock);
if (self.dataArray.count == 0) {
//一旦进入等待状态,就会放开这把锁,直到别人发送信号唤醒
pthread_cond_wait(&_cond, &_lock);
}
[self.dataArray removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_lock);
}
- (void)__add{
pthread_mutex_lock(&_lock);
[self.dataArray addObject:@"123"];
NSLog(@"添加了元素");
pthread_cond_signal(&_cond);
pthread_mutex_unlock(&_lock);
// 这里需要保证pthread_cond_signal在pthread_mutex_unlock之前
// 如果在之后的话,锁解开了,发送信号的这个过程中有可能被别的锁抢先进来了
}
NSLock/NSRecursiveLock/NSCondition
NSLock是对mutex普通锁的封装
NSRecursiveLock是对mutex递归锁的封装NSCondition是对mutex和cont的封装
@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
可以查看GNUStep看到NSLock的实现
@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
Dispatch_queue(DISPATCH_QUEUE_SERIAL) - 串行队列
- 串行队列,也可以实现线程同步,保证了每一条线程的操作都是按顺序的
@property (nonatomic, strong) dispatch_queue_t queen;
self.queen = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
- (void)test{
self.ticketsCount = 20;
dispatch_async(self.queen, ^{
for (int i = 0; i<5; i++) {
[self saleTicket];
}
});
dispatch_async(self.queen, ^{
for (int i = 0; i<5; i++) {
[self saleTicket];
}
});
}
dispatch_semaphore - 信号量
- 信号量的初始值,可以用来控制线程的并发访问的最大数量
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
// 设置最大并发数
self.semaphore = dispatch_semaphore_create(1);
for (int i = 0; i<20; i++) {
[[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]start];
}
- (void)test{
// 如果信号量的值>0,就让信号量减1,然后继续执行下面的代码
// 直到信号量的值<=0的时候,就会休眠等待
// DISPATCH_TIME_FOREVER永远 或者 设置成 DISPATCH_TIME_NOW现在立即
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
self.ticketsCount--;
sleep(2);
NSLog(@"剩下的票为 %d",self.ticketsCount);
//让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@synchronized
@synchronized是对mutex递归锁的封装
@synchronized (self) { //objc_sync_enter
} // objc_sync_exit
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
id2data 方法内部
内部是一个哈希表,把
obj当做key ,data->mutex.lock(); 拿到唯一的mutex锁,来加锁
自旋锁和互斥锁对比
什么情况下使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发送
- CPU资源不紧张
- 多核处理器
什么情况下使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
\