当前位置:   article > 正文

《Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》---学习笔记(41-52)_学习oc的开源代码

学习oc的开源代码

41. 多用派发队列,少用同步锁

OC中,如果多个线程要执行同一份代码,那么可能会出现问题。这种情况下,通常使用锁来实现某种同步机制。

GCD出现之前,有两种方法:

  • 第一种:synchronization
- (void)someMethod
{
	@synchronized(self){
		//Safe
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这种会根据给定的对象(self),自动创建一个锁,并等待块中的代码执行完毕。
执行到这段代码结尾处,锁就释放了。
然鹅,过多的使用@synchronized(self),会降低代码效率,因为共用同一个锁的那些同步块,都必须按顺序执行

  • 第二种:NSLock对象
_lock = [[NSLock alloc] init];
- (void)someMethod
{
	[_lock lock];
	//Safe
	[_lock unlock];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

也可以使用递归锁:NSRecursiverLock,线程可以多次持有该锁,而不会出现死锁(deadlock)现象。

  • 在有GCD之后,同步问题可以使用GCD,它更简单、更高效。

在设置属性的时候,如果需要属性为“原子的”,则经常需要用到同步,使用atomic来修饰属性,就可以实现。
但无法保证访问该对象时绝对是线程安全的。

有种简单而高效的方法可以代替同步块或锁对象,那就是使用“串行同步队列
将读写操作都安排在同一个队列里,即可保证数据同步

例如:
在这里插入图片描述即可实现数据的同步

还可以将设置方法写成异步执行:

在这里插入图片描述

多读单写

GCD有一个栅栏(barrier),可以实现多读单写功能。
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

  • 栅栏只对并发队列有意义,串行队列的话,只是普通的一个一个执行

举个例子:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

- (NSString *)someString
{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

- (void)setSomeString:(NSString *)someString{
    dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

经过与之前写的一篇有关多读单写的文章多线程学习(二)有些地方有冲突,有些地方有记录不清的地方,做统一验证

有几处疑问:

  • 栅栏使用串行队列可以吗?什么效果?
  • 栅栏使用全局并发队列可以吗?(全局并发+dispatch_barrier_async,全局并发+dispatch_barrier_sync)
  • 栅栏使用dispatch_barrier_async和dispatch_barrier_sync是否都可以?
栅栏使用串行队列可以吗?什么效果?

两篇文章都提到了不能使用串行队列
验证:
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_SERIAL);
打印结果:

2022-03-20 11:33:44.526704+0800 008[1941:46200] -[ViewController read]_block_invoke
2022-03-20 11:33:45.528229+0800 008[1941:46200] -[ViewController read]_block_invoke
2022-03-20 11:33:46.530097+0800 008[1941:46200] -[ViewController read]_block_invoke
2022-03-20 11:33:47.535326+0800 008[1941:46200] -[ViewController write]_block_invoke
2022-03-20 11:33:48.540525+0800 008[1941:46200] -[ViewController write]_block_invoke
2022-03-20 11:33:49.544776+0800 008[1941:46200] -[ViewController write]_block_invoke
2022-03-20 11:33:50.546115+0800 008[1941:46200] -[ViewController read]_block_invoke
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看出,读、写都是每秒执行一次,从而失去了多读单写的功能

栅栏使用全局并发队列可以吗?
  • 全局并发+dispatch_barrier_async

验证:

self.queue = dispatch_get_global_queue(0, 0);

- (void)write
{
    dispatch_barrier_async(self.queue, ^{
        sleep(1);
        NSLog(@"%s", __func__);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

结果:

2022-03-20 11:37:00.889954+0800 008[2015:50136] -[ViewController read]_block_invoke
2022-03-20 11:37:00.889936+0800 008[2015:50138] -[ViewController write]_block_invoke
2022-03-20 11:37:00.889946+0800 008[2015:50140] -[ViewController read]_block_invoke
2022-03-20 11:37:00.889989+0800 008[2015:50142] -[ViewController write]_block_invoke
2022-03-20 11:37:00.889960+0800 008[2015:50141] -[ViewController read]_block_invoke
2022-03-20 11:37:00.890017+0800 008[2015:50137] -[ViewController write]_block_invoke
2022-03-20 11:37:00.890017+0800 008[2015:50149] -[ViewController read]_block_invoke
2022-03-20 11:37:00.890041+0800 008[2015:50148] -[ViewController read]_block_invoke
2022-03-20 11:37:00.890098+0800 008[2015:50156] -[ViewController read]_block_invoke
2022-03-20 11:37:00.890096+0800 008[2015:50153] -[ViewController write]_block_invoke
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

基本上一下就执行完了,写是同步进行的,没有休眠,因此,不满足“单写”

  • 全局并发+dispatch_barrier_sync

验证:

self.queue = dispatch_get_global_queue(0, 0);

- (void)write
{
    dispatch_barrier_sync(self.queue, ^{
        sleep(1);
        NSLog(@"%s", __func__);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

结果:

2022-03-20 11:38:08.063348+0800 008[2048:51424] -[ViewController write]_block_invoke
2022-03-20 11:38:08.065732+0800 008[2048:51476] -[ViewController read]_block_invoke
2022-03-20 11:38:08.065732+0800 008[2048:51472] -[ViewController read]_block_invoke
2022-03-20 11:38:08.065732+0800 008[2048:51471] -[ViewController read]_block_invoke
2022-03-20 11:38:09.064681+0800 008[2048:51424] -[ViewController write]_block_invoke
2022-03-20 11:38:10.066012+0800 008[2048:51424] -[ViewController write]_block_invoke
2022-03-20 11:38:11.067604+0800 008[2048:51424] -[ViewController write]_block_invoke
2022-03-20 11:38:11.067633+0800 008[2048:51471] -[ViewController read]_block_invoke
2022-03-20 11:38:11.067635+0800 008[2048:51476] -[ViewController read]_block_invoke
2022-03-20 11:38:11.067635+0800 008[2048:51472] -[ViewController read]_block_invoke
2022-03-20 11:38:12.069155+0800 008[2048:51424] -[ViewController write]_block_invoke
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

读一下就执行完毕,满足多读
写是每一秒执行一次,也满足
但有个小问题,写执行完毕,要执行读的操作的时候,基本上最后一个写和第一个读是同时执行的

栅栏使用dispatch_barrier_async和dispatch_barrier_sync是否都可以?
  • 自定义并发+dispatch_barrier_sync

验证:

self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

- (void)write
{
    dispatch_barrier_sync(self.queue, ^{
        sleep(1);
        NSLog(@"%s", __func__);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

结果:

2022-03-20 11:41:08.055321+0800 008[2114:54371] -[ViewController read]_block_invoke
2022-03-20 11:41:08.055321+0800 008[2114:54372] -[ViewController read]_block_invoke
2022-03-20 11:41:08.055321+0800 008[2114:54376] -[ViewController read]_block_invoke
2022-03-20 11:41:09.056482+0800 008[2114:54163] -[ViewController write]_block_invoke
2022-03-20 11:41:10.058033+0800 008[2114:54163] -[ViewController write]_block_invoke
2022-03-20 11:41:11.059538+0800 008[2114:54163] -[ViewController write]_block_invoke
2022-03-20 11:41:12.060971+0800 008[2114:54371] -[ViewController read]_block_invoke
2022-03-20 11:41:12.060979+0800 008[2114:54376] -[ViewController read]_block_invoke
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

满足多读单写

  • 自定义并发+dispatch_barrier_async
    验证:
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

- (void)write
{
    dispatch_barrier_async(self.queue, ^{
        sleep(1);
        NSLog(@"%s", __func__);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

结果:

2022-03-20 11:42:58.414861+0800 008[2159:56378] -[ViewController read]_block_invoke
2022-03-20 11:42:58.414861+0800 008[2159:56382] -[ViewController read]_block_invoke
2022-03-20 11:42:58.414861+0800 008[2159:56383] -[ViewController read]_block_invoke
2022-03-20 11:42:59.420234+0800 008[2159:56382] -[ViewController write]_block_invoke
2022-03-20 11:43:00.425576+0800 008[2159:56382] -[ViewController write]_block_invoke
2022-03-20 11:43:01.426410+0800 008[2159:56382] -[ViewController write]_block_invoke
2022-03-20 11:43:02.431424+0800 008[2159:56378] -[ViewController read]_block_invoke
2022-03-20 11:43:02.431423+0800 008[2159:56380] -[ViewController read]_block_invoke
2022-03-20 11:43:02.431445+0800 008[2159:56382] -[ViewController read]_block_invoke
2022-03-20 11:43:03.436903+0800 008[2159:56378] -[ViewController write]_block_invoke
2022-03-20 11:43:04.440432+0800 008[2159:56378] -[ViewController write]_block_invoke
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

总结:

  • 栅栏不能使用串行队列
  • 使用自定义并发队列+同步/异步栅栏都可以实现多读单写
  • 使用全局并发队列+异步栅栏不可以
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/276861
推荐阅读
相关标签