温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

iOS使用多线程提高数据并发访问 之六

发布时间:2020-06-30 06:25:07 来源:网络 阅读:1748 作者:iKingLai 栏目:移动开发

线程的风险


当运行在一个多线程环境中,你总是需要注意一些事情:你不能控制线程执行的顺序。例如,如果你有两个线程,线程1和线程2,CPU可能会在线程1上运行一段时间,然后又会切到线程2运行一段时间。问题是你不知道CPU何时切过去,也不知道会为一个线程分配多少时间。每个线程运行的时间都不是公平的。


为了演示线程不容易控制带来的风险,我会举一个例子。这个例子包括两个线程:线程1和线程2. 线程1打印奇数,线程2打印偶数。这些数的范围从1到20. 线程1先启动,然后线程2再启动。这个例子将会运行3次。Listing 6-5 显示了样例代码。


iOS使用多线程提高数据并发访问 之六


正如你看到的,我先调用打印奇数,然后再打印偶数。你可能会期待看到

  • 先打印一些奇数

  • 奇数和偶数平均打印,例如两个奇数和两个偶数


但是,这些猜测都是不正确的,你可以看表格 6-4, 显示了3次运行的结果。


iOS使用多线程提高数据并发访问 之六


你看下第2次,0先打印出来,而其他的都是先打印1。奇数和偶数打印也不是平均的;而且,也没什么迹象表明有多少奇数在偶数之前打印。


因此,在多线程环境中,你不能控制线程执行的顺序。多线程是一把双刃剑。开发者实现一个多线程应用需要注意下面的3个风险。

  • 安全性:这个标准意味着在多线程环境下,输出要跟预期的一致。换句话说,程序可以运行在不同的顺序中多次,但是最终的输出必须是可预见的,正确的。“糟糕的事情不会发生”。

  • 活跃性:这个和安全性不同。一种定义是“一些好的情况最终会发生”。例如,假设线程A必须等到线程B的结果,有时这些结果从来都不返回。因此,线程A从来不会计算最终结果。这个通常称为死锁。

  • 性能:iPhone应用最重要的一个目标就是有一个比较好的性能和更灵敏的UI。因此,你的性能目标必须达到。活跃性只关注一些最终发生的事情;它并不关心多快获取到结果。


我将会在接下来的部分使用例子来涉及到每一个标准,这样你就可以理解什么样会导致一个不好的结果,你如何解决它,使得你的应用运行时有一个比较高的性能。


安全性

安全性要求程序运行在多线程环境中,产生一个正确的期待的结果,就想他运行在单线程环境中一样。我会讨论一个潜在的在多线程环境中经常会发生的一个问题,当两个或多个线程同时访问相同的数据。


图 6-5 描述了两个线程如何返回一个相同的item而导致应用崩溃。在图6-5中,线程1尝试把item push到当前栈中。然后,线程2和线程3想要把item取出来,然后检查确保这个item在这个栈中。但是,在两个线程检查之后,线程2先运行,然后获取item。Oops!像你看到的,线程3已经没有item可以获取了。这会导致你的应用崩溃。


iOS使用多线程提高数据并发访问 之六

你可以从ThreadSafety工程中获取到样例代码,但是Listing 6-6 显示了这个问题的代码注解。注意这个问题不会总是出现,但是如果你运行足够多的时间,它还是会发生的。代码使用了NSMutableArray变量存储,因此客户端代码能够添加和删除数据。


iOS使用多线程提高数据并发访问 之六

iOS使用多线程提高数据并发访问 之六


当你运行上面的代码一段时间,你会收到下面的信息:

iOS使用多线程提高数据并发访问 之六

它告诉你,你尝试在一个空的数组中删除对象,这是不应该发生的当你在删除之前已经检查了数组空的情况。你甚至打印出来看它是否是最后一个对象。


现在,如果你再一次查看图6-4,你应该理解为什么会崩溃 -- 因为第一个现场检查和打印出最后一个对象后,第一个线程已经停止,而第二个线程还在运行。


解决办法


对于这个问题我的解决办法是锁住这个方法直到线程执行完。锁是一种机制,它能够确保在一个时间内只有一个线程访问一个指定的代码块。想象一下,你现在在一个比赛中,需要直接和很多人竞争。你和你的竞争者被问了一个问题,而谁先响铃谁就能先回答问题。在第一个结束之后,另外一个人又可以摇铃了。当第一个人在回答问题时,这个铃是被锁住的。线程也是一样的。你可以创建一个锁,就像是你的铃一样:第一个得到锁的线程(类似摇铃)会阻塞其他所有的线程直到它结束。在第一个线程结束之后,锁就开了(类似于其他人可以摇铃了);其他线程能尝试获取锁,而这个过程可以重复下去。


锁机制的基本概念就是确保当一个线程在执行任务时,其他线程不能打断。例如,如果线程1获取到这个对象,然后打印出来,线程2必须等到线程1执行完才能获取和从数组中删除对象。


这个锁创建在一个对象上。如果线程1从对象A获取了锁,其他线程就不能再获取这个对象的锁了,这些线程必须等待线程1执行完后,然后把锁返回给对象A。


最简单的方式获取对象A的锁的就是使用@synchronized(objA),如下面Listing 6-7 的代码。


iOS使用多线程提高数据并发访问 之六

iOS使用多线程提高数据并发访问 之六


注意:在很多情况下,使用self作为锁,效果是一样的。你只需要确保你想要锁住的对象使用同一个对象锁即可。例如,你有两个存储变量,你可以考虑为每一个单独是有关一个锁。



图6-6 显示了@synchronized在线程中是如何工作的。


iOS使用多线程提高数据并发访问 之六

你需要同时同步push和pop data这两个方法,因为如果你只锁住其中的一个方法,当你pop检查时,依然会存在风险,还有存储器push了很多数据,而你不没有按照你想要的方式获取到对象。为了防止这些,你需要使用lockObj同时锁住他们,这样在一个时间段就只有一个方法在运行。


你的代码是安全的,但是依然还有两个更重要的多线程属性需要讨论。



向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI