解决资源竞争的方法,控制线程同步

文章目录
  1. 1.
    1. 1.1. OSSpinLoc
    2. 1.2. os_unfair_lock
    3. 1.3. pthread_mutex
      1. 1.3.1. 递归锁
      2. 1.3.2. condition锁
    4. 1.4. NSLock,NSRecursiveLock,NSConditionLock
    5. 1.5. dispatch_semaphore
    6. 1.6. @synchronized
    7. 1.7. atomic
    8. 1.8. pthread_rwlock
  2. 2. 其他同步的手段
    1. 2.1. Group
    2. 2.2. 同步队列
    3. 2.3. Thread barrier
    4. 2.4. dependency
      1. 2.4.1. WorkItem
      2. 2.4.2. Operation
  3. 3. 管理单例

如何检查资源竞争的问题?
打开 thread Sanitizer 然后 run project

锁是操作系统解决资源冲突的一种机制,用来控制线程同步
在 apple 平台中使用的锁有

OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
automic

源码: GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍

OSSpinLoc

  1. OSSpinLock(自旋锁),等待锁的线程会处于忙等(while(1))(不是内核级的轮询触发中断处理事件,而是用户级别的强占cpu资源)
  2. 不再安全,可能会出现优先级反转问题
    1. thread1.priority > thread2.priority
    2. thread1 得到cpu的时间片的几率多很多,没有获取lock,所以出于 while(1)
    3. thread2 此时拿到 lock,可是拿不到 cpu,没办法执行

os_unfair_lock

os_unfair_lock用于代替的OSSpinLock(iOS >= 10)
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func os_unfair_lock_() {
var lock = os_unfair_lock()
var a = 0
let globalQueue = DispatchQueue(label: "test", attributes: .concurrent)
let group = DispatchGroup()
while a<3 {
globalQueue.async(group: group, execute: DispatchWorkItem(block: {
print("\(#function)---thread: \(Thread.current) value:\(a)")
os_unfair_lock_lock(&lock)
a += 1
os_unfair_lock_unlock(&lock)
}))
}
print("value:\(a)")
group.notify(queue: globalQueue) {
print("final result: \(a)")
}
}

pthread_mutex

互斥锁,等待锁的线程会处于休眠状态

1
2
3
4
5
6
7
8
9
10
11
12
13
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);

// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);

递归锁

递归锁:允许同一个线程对一把锁进行重复加锁,当同一把锁上锁解锁的个数相同时,其他线程可继续执行

1
2
3
4
5
6
7
8
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);

condition锁

1
2
3
4
5
6
7
8
9
10
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// 初始化条件
pthread_cond_init(&_cond, NULL);

生产者-消费者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 线程1
// 删除数组中的元素
- (void)remove {
pthread_mutex_lock(&_mutex);
NSLog(@"remove - begin");
if (self.data.count == 0) {// 等待
pthread_cond_wait(&_cond, &_mutex);
}

[self.data removeLastObject];
pthread_mutex_unlock(&_mutex);
}

// 线程2
// 往数组中添加元素
- (void)add {
pthread_mutex_lock(&_mutex);

sleep(1);

[self.data addObject:@"Test"];

// 信号
pthread_cond_signal(&_cond);
// 广播,激活所有等待该条件的锁
// pthread_cond_broadcast(&_cond);

pthread_mutex_unlock(&_mutex);
}

NSLock,NSRecursiveLock,NSConditionLock

NSLock: mutex(PTHREAD_MUTEX_DEFAULT) 的封装
NSRecursiveLock: mutex(PTHREAD_MUTEX_RECURSIVE) 的封装
NSConditionLock: mutex 和 cond 的封装

dispatch_semaphore

信号量
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,同 mutex,代表同时只允许1条线程访问资源,保证线程同步

1
2
3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
int res = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);// 第二个参数是,等待时间,如果超过该时间thread 就会穿过这个信号量(res != 0)
dispatch_semaphore_signal(semaphore);

@synchronized

内部是 os_unfair_lock 实现的

看下 objc4-756.2 objc-sync.mm 的源码

1
2
3
4
5
6
7
8
9
10
11
// synchronized 调用
objc_sync_enter(obj)
objc_sync_exit(obj)

// 内部根据对象从字典中拿到 该对象数据列表&锁
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);

spinlock_t ??

在看下

1
2
using spinlock_t = mutex_tt<LOCKDEBUG>;
using mutex_t = mutex_tt<LOCKDEBUG>;

mutex_tt ??

1
2
3
4
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
...
};

atomic

atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
看下 objc4-756.2 objc-accessors.mm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}

// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;

// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();

// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy){
if (offset == 0) {
object_setClass(self, newValue);
return;
}

id oldValue;
id *slot = (id*) ((char*)self + offset);

if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}

if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}

objc_release(oldValue);
}

它并不能保证使用属性的过程是线程安全的
eg: NSMutableArray 添加元素,线程不安全

pthread_rwlock

读写锁, 读操作可以并发执行,但是 写操作只能一个个执行,写操作期间不能 read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 // 初始化锁
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}

- (void)read {
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);

pthread_rwlock_unlock(&_lock);
}

- (void)write {
pthread_rwlock_wrlock(&_lock);

sleep(1);
NSLog(@"%s", __func__);

pthread_rwlock_unlock(&_lock);
}

其他同步的手段

Group

1
2
3
4
5
6
7
8
9
10
11
12
let group = DispatchGroup() 
someQueue.async(group: group) { ... your work ... } someQueue.async(group: group) { ... more work .... } someOtherQueue.async(group: group) { ... other work ... }

// 异步通知
group.notify(queue: DispatchQueue.main) { [weak self] in
self?.textLabel.text = "All jobs have completed"
}

// 同步等待
if group.wait(timeout: .now() + 60) == .timedOut {
print("The jobs didn’t finish in 60 seconds")
}
1
2
3
4
5
6
queue.dispatch(group: group) {
// count is 1
group.enter()
// count is 2
someAsyncMethod { defer { group.leave() } }
}

同步队列

1
2
3
4
5
6
7
8
9
private let threadSafeCountQueue = DispatchQueue(label: "...")
private var _count = 0
public var count: Int {
get {
return threadSafeCountQueue.sync { _count }
}
set {
threadSafeCountQueue.sync { _count = newValue }
}

Thread barrier

1
2
3
4
5
6
7
8
9
10
11
12
private let threadSafeCountQueue = DispatchQueue(label: "...",attributes: .concurrent) 
private var _count = 0
public var count: Int {
get {
return threadSafeCountQueue.sync { return _count }
}
set {
threadSafeCountQueue.async(flags: .barrier) {[unowned self] in
self._count = newValue
}
}
}

dependency

WorkItem

1
2
3
4
5
6
let queue = DispatchQueue(label: "xyz") 
let backgroundWorkItem = DispatchWorkItem { }
let updateUIWorkItem = DispatchWorkItem { }

backgroundWorkItem.notify(queue: DispatchQueue.main,execute: updateUIWorkItem)
queue.async(execute: backgroundWorkItem)

Operation

addDependance

管理单例

单例是个让人又爱又恨的东西
单例不是线程安全的,当多个 controllers 同时调用单例的时候,很容易引起线程冲突

swift 中实现单例很容易

1
2
3
4
class PhotoManager {
private init() {}
static let shared = PhotoManager()
}

在 swift 中

  • let 修饰的变量 thread-safe
  • var not thread-safe
  • Array and Dictionary are not thread-safe when declared mutable.
  • struct 是值类型,赋值过程都会发成 值copy,所以值类型的数据是线程安全的
  • swift 值copy是写时 copy(eg: 改变 array 中的一个元素)