demo: ThreadSynchronization
多线程技术使得执行任务的效率得到提升,但多线程也是一个易发生各种问题的编程技术。如数据竞争(多个线程更新相同资源导致数据不一致)、死锁(多个线程相互等待)、太多线程会销毁大量内存等问题。数据竞争需要通过线程同步方案来解决,一般使用锁解决对某项资源的互斥使用,下面列举些iOS中的线程同步技术。
一、OSSpinLock
使用OSSpinLock,等待锁的线程会处于忙等状态,一直占用CPU资源;
现在已经不再安全,苹果已经弃用,可能会出现优先级翻转问题;(如果等待锁的线程优先级高,它会一直占用CPU资源,导致优先级低的线程无法释放锁)
使用时需导入头文件 #import <libkern/OSAtomic.h>
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 加锁
OSSpinLockLock(&_lock);
// 尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
bool result = OSSpinLockTry(&_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_lock(&_lock);
// 尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
bool result = os_unfair_lock_trylock(&_lock);
//解锁
os_unfair_lock_unlock(&_lock);
三、pthread_mutex_t
mutex叫做“互斥锁”,等待锁的线程会处于休眠状态;
使用时需导入头文件 #import <pthread.h>
初始化属性pthread_mutexattr_t时,Mutex type设为PTHREAD_MUTEX_NORMAL和PTHREAD_MUTEX_DEFAULT时代表是互斥锁,设置为PTHREAD_MUTEX_RECURSIVE代表递归锁(加锁代码递归调用时使用,pthread_mutexattr_t的递归锁只有当同一线程中才能重复加锁,不同线程仍需等待解锁后才能加锁);初始化锁方法pthread_mutex_init中attr字段传入NULL代表默认属性PTHREAD_MUTEX_DEFAULT;
/* 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
*/
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(&mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// 或
// attr传入NULL代表 PTHREAD_MUTEX_DEFAULT
//pthread_mutex_init(&mutex, NULL);
// 尝试加锁
// pthread_mutex_trylock(&mutex);
// 加锁
pthread_mutex_lock(&_mutex);
// 解锁
pthread_mutex_unlock(&_mutex);
// 销毁锁
pthread_mutex_destroy(&(_mutex));
四、pthread_cond_t
pthread_cond_t配合pthread_mutex_t使用
// 初始化条件
pthread_cond_init(&_cond, NULL);
// 等待条件cond,线程进入休眠状态并解锁mutex,待线程唤醒后会重新加锁线程往下继续执行
pthread_cond_wait(&_cond, &_mutex);
// 激活一个等待条件cond的线程,
pthread_cond_signal
// 激活所有等待条件cond的线程
pthread_cond_broadcast
下面举个例子,如下代码假设线程1执行removeItem,但初始时self.dataArray为空,于是等待条件并解锁,线程2执行addItem调用后,self.dataArray不再为空且激活等待cond的线程1,线程1加锁执行removeItem。
- (void)operateDataArray {
[[[NSThread alloc]initWithTarget:self selector:@selector(removeItem) object:nil] start];
sleep(.2);
[[[NSThread alloc]initWithTarget:self selector:@selector(addItem) object:nil] start];
}
- (void)addItem {
pthread_mutex_lock(&_mutex);
[self.dataArray addObject:@"item"];
NSLog(@"添加后:%@",self.dataArray);
pthread_cond_signal(&_cond);
// pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);
}
- (void)removeItem {
pthread_mutex_lock(&_mutex);
if (self.dataArray.count == 0) {
NSLog(@"等待");
pthread_cond_wait(&_cond, &_mutex);
}
[self.dataArray removeObject:@"item"];
NSLog(@"移除后:%@",self.dataArray);
pthread_mutex_unlock(&_mutex);
}
四、NSLock
NSLock是对普通pthread_mutex_t的封装,使用非常简单;
@protocol NSLocking
- (void)lock;// 加锁
- (void)unlock;// 解锁
@end
@interface NSLock : NSObject <NSLocking> {// NSLock 遵循NSLocking协议
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;//在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
// 初始化
NSLock *lock = [[NSLock alloc]init];
五、NSRecursiveLock
NSRecursiveLock是对pthread_mutex_t递归锁的封装,API跟NSLock类似。
@interface NSRecursiveLock : NSObject <NSLocking> {// 同样遵循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
// 初始化
self.recursiveLock = [[NSRecursiveLock alloc]init];
六、NSCondition
NSCondition是对mutex和cond的封装
NS_CLASS_AVAILABLE(10_5, 2_0)
@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
八、dispatch_semaphore_t
dispatch_semaphore_t通过控制信号量也可以实现线程同步。对dispatch_semaphore_t的说明可查看多线程总结.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.semaphore = dispatch_semaphore_create(1);
__weak typeof(DispatchSemaphoreController) *weakSelf = self;
dispatch_queue_t queue = dispatch_queue_create("XL.LockPractice.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[weakSelf saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[weakSelf withDrayMoney];
}
});
}
- (void)saveMoney {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
[super saveMoney];
dispatch_semaphore_signal(self.semaphore);
}
- (void)withDrayMoney {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
[super withDrayMoney];
dispatch_semaphore_signal(self.semaphore);
}
九、GCD串行队列
GCD串行队列,保证事件按顺序一个个执行,GCD队列使用参考多线程总结;
十、 @synchronized
@synchronized是对mutex递归锁的封装,可以查看Runtime源码objc-sync.mm文件。@synchronized(obj)内部会根据objc生成对应的递归锁,并进行加锁解锁操作。注意需要保证obj的唯一性,不同obj生成的锁是不同的。
@synchronized (obj) {// objc_sync_enter
// task
} // objc_sync_exit
下面是源码中objc_sync_enter和objc_sync_exit的实现。从源码和注释中可以看出,objc_sync_enter是根据obj生成一个对象的mutex递归锁;
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//obj存在,根据obj生成一个锁,并调用加锁方法lock(), SyncData
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {// 如果obj为空,doing nothing
// @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;
}
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {// 根据obj取出锁,并尝试解锁
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
网友评论