【发布时间】:2018-08-12 14:32:48
【问题描述】:
我正在尝试实现一个线程安全的电话簿对象。电话簿应该能够添加一个人,并根据他们的姓名和电话号码查找一个人。从实现的角度来看,这仅涉及两个哈希表,一个关联名称 -> 个人,另一个关联电话号码 -> 个人。
需要注意的是我希望这个对象是线程安全的。这意味着我希望能够支持 PhoneBook 中的并发查找,同时确保一次只有一个线程可以将 Person 添加到 PhoneBook。这是基本的读写器问题,我正在尝试使用 GrandCentralDispatch 和调度障碍来解决这个问题。我正在努力解决这个问题,因为我遇到了问题。下面是我的 Swift 游乐场代码:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
public class Person: CustomStringConvertible {
public var description: String {
get {
return "Person: \(name), \(phoneNumber)"
}
}
public var name: String
public var phoneNumber: String
private var readLock = ReaderWriterLock()
public init(name: String, phoneNumber: String) {
self.name = name
self.phoneNumber = phoneNumber
}
public func uniquePerson() -> Person {
let randomID = UUID().uuidString
return Person(name: randomID, phoneNumber: randomID)
}
}
public enum Qos {
case threadSafe, none
}
public class PhoneBook {
private var qualityOfService: Qos = .none
public var nameToPersonMap = [String: Person]()
public var phoneNumberToPersonMap = [String: Person]()
private var readWriteLock = ReaderWriterLock()
public init(_ qos: Qos) {
self.qualityOfService = qos
}
public func personByName(_ name: String) -> Person? {
var person: Person? = nil
if qualityOfService == .threadSafe {
readWriteLock.concurrentlyRead { [weak self] in
guard let strongSelf = self else { return }
person = strongSelf.nameToPersonMap[name]
}
} else {
person = nameToPersonMap[name]
}
return person
}
public func personByPhoneNumber( _ phoneNumber: String) -> Person? {
var person: Person? = nil
if qualityOfService == .threadSafe {
readWriteLock.concurrentlyRead { [weak self] in
guard let strongSelf = self else { return }
person = strongSelf.phoneNumberToPersonMap[phoneNumber]
}
} else {
person = phoneNumberToPersonMap[phoneNumber]
}
return person
}
public func addPerson(_ person: Person) {
if qualityOfService == .threadSafe {
readWriteLock.exclusivelyWrite { [weak self] in
guard let strongSelf = self else { return }
strongSelf.nameToPersonMap[person.name] = person
strongSelf.phoneNumberToPersonMap[person.phoneNumber] = person
}
} else {
nameToPersonMap[person.name] = person
phoneNumberToPersonMap[person.phoneNumber] = person
}
}
}
// A ReaderWriterLock implemented using GCD and OS Barriers.
public class ReaderWriterLock {
private let concurrentQueue = DispatchQueue(label: "com.ReaderWriterLock.Queue", attributes: DispatchQueue.Attributes.concurrent)
private var writeClosure: (() -> Void)!
public func concurrentlyRead(_ readClosure: (() -> Void)) {
concurrentQueue.sync {
readClosure()
}
}
public func exclusivelyWrite(_ writeClosure: @escaping (() -> Void)) {
self.writeClosure = writeClosure
concurrentQueue.async(flags: .barrier) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.writeClosure()
}
}
}
// MARK: Testing the synchronization and thread-safety
for _ in 0..<5 {
let iterations = 1000
let phoneBook = PhoneBook(.none)
let concurrentTestQueue = DispatchQueue(label: "com.PhoneBookTest.Queue", attributes: DispatchQueue.Attributes.concurrent)
for _ in 0..<iterations {
let person = Person(name: "", phoneNumber: "").uniquePerson()
concurrentTestQueue.async {
phoneBook.addPerson(person)
}
}
sleep(10)
print(phoneBook.nameToPersonMap.count)
}
为了测试我的代码,我运行了 1000 个并发线程,这些线程只是将一个新人添加到电话簿。每个 Person 都是唯一的,因此在 1000 个线程完成后,我希望 PhoneBook 包含 1000 个计数。每次执行写入时,我都会执行 dispatch_barrier 调用,更新哈希表并返回。据我所知,这就是我们需要做的。但是,在重复运行 1000 个线程后,我发现 PhoneBook 中的条目数不一致并且到处都是:
Phone Book Entries: 856
Phone Book Entries: 901
Phone Book Entries: 876
Phone Book Entries: 902
Phone Book Entries: 912
谁能帮我弄清楚发生了什么?我的锁定代码是否有问题,或者更糟糕的是,我的测试是如何构建的?我对这个多线程问题空间很陌生,谢谢!
【问题讨论】:
-
我认为你的问题是
sleep(10)是一个竞争条件。在需要等到所有工作完成的实际应用程序中,您不会使用它。尝试sleep(30)作为实验,看看您的结果是否有所改善。 -
我怎么说等到所有工作都在现实生活中完成?我也觉得关于睡眠的草图。 sleep(30) 也不起作用..
-
顺便说一句,我假设这个模型只是为了探索线程安全,但我可能会建议一个稍微不同的结构,即
[Person]的数组,然后具有返回人员数组的方法给定的电话号码或名字(例如使用filter)。我们中的许多人都有多个联系人使用相同的电话号码(例如,共享的业务线,甚至是拥有相同家庭电话的家庭)。我什至为同名的不同人提供了一些条目(例如,我有两个不同的“保罗·威廉姆斯”,他们是真正不同的人)。做任何你想做的事,但只是一个想法。
标签: swift multithreading grand-central-dispatch data-synchronization barrier