Design Patterns in Swift:单例模式(中)

来源:互联网 发布:矩阵分析与计算 pdf 编辑:程序博客网 时间:2024/06/06 03:06

转自: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会带来什么样的后果以及如果应对可能出现的问题。

潜在的多线程问题在项目中经常见到,包括单例模式(上)中涉及到的LoggerbackupServer类都存在线程安全问题。因为他们涉及对数组进行操作,数组在Swift中是线程不安全的,Swift数组的扩容会重新创建一个新的数组(意味着地址完全不同)。然后ARC会为我们自动释放旧的数组,但这时候可能另一个线程还在访问旧的数组对象,极有可能crash(如果没有是你运气太好)

并发操作Singleton

在上一节的代码基础上进行修改

import Foundationvar 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掉了,并且debuggerdata.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 Foundationclass 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(),每一个时间点只有一个线程在操作数组。

我们上一节主要解决了BackupServerLogger两个类的单例问题,同样这里,也需要对两个类都进行线程安全的保护,虽然在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

如果叙述存在问题,请联系我,谢谢


0 0
原创粉丝点击