在上一篇文章中,我们主要了解了多线程安全隐患,以及它的解决方案。其中,我们有了解到自旋锁和互斥锁,这两种经常用到的锁。这里有一个大致的总结,看看这两个锁在什么情况下比较合适:
1、什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
2、预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
接下来,我们再看另一个知识点,是关于读写安全方面的知识,首先我们来认识下atomic这个修饰符,在iOS这个修饰符的作用就是保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁,它能保证在单个读和写的过程中是安全的,但是它并不能保证使用属性的过程是线程安全的。
这里,我们就针对这个使用属性过程的线程安全问题来看下,首先,我们需要了解方案的具体要求。大致如下:
1、同一时间,只能有1个线程进行写的操作。
2、同一时间,允许有多个线程进行读的操作。
3、同一时间,不允许既有写的操作,又有读的操作。
总结一句话的意思,就是“多读单写”,就是可以多条线程同时读取,但是在写入的时候,只能有一条线程在操作这个属性。iOS的常用实现方案有:
1、pthread_rwlock:读写锁
2、dispatch_barrier_async:异步栅栏
先来看看C语言中的读写锁pthread_rwlock的使用:
- (void)demo {
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(@"read");
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"write");
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc {
pthread_rwlock_destroy(&_lock);
}
打印结果如下图:
image.png
我们可以很清楚的发现,只有在read操作的时候,是有多条线程同时打印的,任何write操作,都是会有1秒钟的间隔。
接下来,我们来看看异步栅栏操作:
- (void)demo {
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self read];
});
dispatch_barrier_async(queue, ^{
[self write];
});
};
}
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
打印结果如下:
image.png
也是我们所希望看到的结果。
不过这里有个必要的点,我们需要了解下,就是创建的queue必须是自定义的DISPATCH_QUEUE_CONCURRENT,如果是自定义串行队列或者直接使用全局并发队列,那么dispatch_barrier_async这个函数等同于dispatch_async!
以上就是这次对多线程知识点总结的补充内容!
网友评论