前言
Data race
(数据竞争)的发生条件:
两个或者更多线程在一个程序中,并发的访问同一数据
至少一个访问是写入操作
些线程都不使用任何互斥锁来控制这些访问
对数据的读取和修改产生了竞争,从而导致各种不可预计的问题。
简单例子
- (void)threadNotSafe {
__block NSInteger total = 0;
for (NSInteger index = 0; index < 3; index++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 加锁
total += 1;
NSLog(@"index: %ld total: %ld", index, total);
total -= 1;
NSLog(@"index: %ld total: %ld", index, total);
// 解锁
});
}
}
若不加锁,total的值将出现错误
简单概念
1.临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。
2.自旋锁:是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
3.互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。
4.读写锁:是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。
5.信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
6.条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
互斥锁
1).NSLock:
@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
tryLock和lock方法都会请求加锁,唯一不同的是trylock在没有获得锁的时候可以继续做一些任务和处理。lockBeforeDate:方法也比较简单,就是在limit时间点之前获得锁,没有拿到返回NO。
2).pthread_mutex:
导入头文件 < pthread.h >
pthread_mutex_t _lock;
pthread_mutex_init(&_lock, NULL);
pthread_mutex_lock(&_lock);
pthread_mutex_unlock(&_lock);
3).@synchronized:
@synchronized(self) {
//数据操作
}
自旋锁
1).OSSpinLock:
导入头文件 < libkern/OSSpinLockDeprecated.h >
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
OSSpinLockUnlock(&lock);
已经废弃了,它在某一些场景下已经不安全了。
2).os_unfair_lock:
os_unfair_lock 是苹果官方推荐的替换OSSpinLock的方案,但是它在iOS10.0以上的系统才可以调用。
导入头文件 < os/lock.h >
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
读写锁(共享-互斥锁)
pthread_rwlock:
导入头文件 < pthread.h >
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
//加读锁
pthread_rwlock_rdlock(&rwlock);
//加写锁
pthread_rwlock_wrlock(&rwlock);
//解锁
pthread_rwlock_unlock(&rwlock);
递归锁
递归锁有一个特点,就是同一个线程可以加锁N次而不会引发死锁。
1).NSRecursiveLock:
与NSLock一样实现了NSLocking协议
2).pthread_mutex(recursive):
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
条件锁
1).NSCondition:
遵循NSLocking协议,使用的时候同样是lock,unlock加解锁,wait是傻等,waitUntilDate:方法是等一会,都会阻塞掉线程,signal是唤起一个在等待的线程,broadcast是广播全部唤起。
2).NSConditionLock:
基本同 NSCondition。
3).信号量
dispatch_semaphore:
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(lock);