转自:http://115.159.192.200/2016/01/14/design-patterns-in-swift-dan-li-mo-shi-zhong/
如有侵犯,请来信oiken@qq.com
这里的阐述是基于Design Patterns in Swift:单例模式(上),当单例模式与多线程碰头,两者应该如何愉快的合作呢?
处理并发情况
如果你正在一个多线程的环境下使用Singleton
,那么你需要去考虑不同模块同一时间去操作Singleton
会带来什么样的后果以及如果应对可能出现的问题。
潜在的多线程问题在项目中经常见到,包括单例模式(上)中涉及到的Logger
和backupServer
类都存在线程安全问题。因为他们涉及对数组进行操作,数组在Swift中是线程不安全的,Swift数组的扩容会重新创建一个新的数组(意味着地址完全不同)。然后ARC会为我们自动释放旧的数组,但这时候可能另一个线程还在访问旧的数组对象,极有可能crash
(如果没有是你运气太好)
并发操作Singleton
在上一节的代码基础上进行修改
import Foundation
var server = BackupServer.server;
let queue = dispatch_queue_create("workQueue", DISPATCH_QUEUE_CONCURRENT);
let group = dispatch_group_create();
for count in 0 ..< 100 {
dispatch_group_async(group, queue, {() in
BackupServer.server.backup(DataItem(type: DataItem.ItemType.Email,
data: "bob@example.com"))
}); }
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
println("\(server.getData().count) items were backed up");
这里是使用了GCD的方式来进行多线程操作,调用backup
方法100次。虽然对GCD有一些了解,但是非常碎片化,最近两天打算 总结一下GCD的用法,这了先简单介绍一下代码中用到的知识。
let queue = dispatch_queue_create("workQueue", DISPATCH_QUEUE_CONCURRENT);
代表创建一个名为workQueue
的并发队列
let group = dispatch_group_create();
代表创建一个GCD任务组
for count in 0 ..< 100 {
dispatch_group_async(group, queue, {() in
BackupServer.server.backup(DataItem(type: DataItem.ItemType.Email,
data: "bob@example.com"))
}); }
把队列queue放到调度组中以异步的方式执行,代表100个闭包的内容可以同时执行,不会因为其他block而阻塞。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
代表会等待到group中的所有任务都完成再去执行下面的代码。
我们运行一下程序,发现crash
掉了,并且debugger
在data.append()
方法处中断(break),上面说到了数组扩容的原理,对于线程不安全的操作导致crash
(错误提示:manipulating the contents of a Swift array isn’t a thread-safe operation, and singletons that use arrays need concurrency protections.)
串行操作
为了解决这个问题,我需要确定在一个时间点只有一个block去调用backup
方法来对数组进行扩容。在这里采取了GCD的串行方式。
import Foundation
class DataItem {
enum ItemType : String {
case Email = "Email Address";
case Phone = "Telephone Number";
case Card = "Credit Card Number";
}
var type:ItemType;
var data:String;
init(type:ItemType, data:String) {
self.type = type; self.data = data;
} }
final class BackupServer {
let name:String;
private var data = [DataItem]();
private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);
private init(name:String) {
self.name = name;
globalLogger.log("Created new server \(name)");
}
func backup(item:DataItem) {
dispatch_sync(arrayQ, {() in
self.data.append(item);
globalLogger.log("\(self.name) backed up item of type \(item.type.rawValue)");
})
}
func getData() -> [DataItem]{
return data;
}
class var server:BackupServer {
struct SingletonWrapper {
static let singleton = BackupServer(name:"MainServer");
}
return SingletonWrapper.singleton;
}
}
对部分代码进行分析
private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);
创建名为arrayQ
的串行队列
dispatch_sync(arrayQ, {() in
self.data.append(item);
globalLogger.log("\(self.name) backed up item of type \(item.type.rawValue)");
})
同步访问data.append()
,每一个时间点只有一个线程在操作数组。
我们上一节主要解决了BackupServer
和Logger
两个类的单例问题,同样这里,也需要对两个类都进行线程安全的保护,虽然在BackupServer
中我们是同步的调用globalLogger.log()
方法,但是可能在其他模块也调用了globalLogger.log()
方法,那么我们就需要在Logger
类中进行修改保护。
import Foundation;
let globalLogger = Logger();
final class Logger {
private var data = [String]()
private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);
private init() {
// do nothing - required to stop instances being
// created by code in other files
}
func log(msg:String) {
dispatch_sync(arrayQ, {() in
self.data.append(msg);
});
}
func printLog() {
for msg in data {
println("Log: \(msg)");
}
} }
这里也使用相同的方法创建串行队列,同步方法确认一个时间点只有一个线程对数组进行修改操作。
这时候运行我们的程序正常结果被打印
100 items were backed up
如果叙述存在问题,请联系我,谢谢