我正在尝试实现一个线程安全的电话簿对象。该电话簿应能够添加人员,并根据其姓名和电话号码查找人员。从实现的角度来看,这仅涉及两个哈希表,一个将名称->Person相关联,另一个将电话号码->Person相关联。
但有一个注意事项,我希望此对象是线程安全的。这意味着我希望能够支持PhoneBook中的并发查找,同时确保只有一个线程可以一次向PhoneBook添加Person。这是基本的读者-写者问题,我正在尝试使用GrandCentralDispatch和dispatch barriers来解决这个问题。但是我遇到了问题,以下是我的Swift playground代码:
//: 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个并发线程,每个线程简单地向电话簿中添加一个新的联系人。每个联系人都是唯一的,因此在这1000个线程完成后,我期望电话簿中包含1000个联系人。每次执行写操作时,我会执行dispatch_barrier调用、更新哈希表并返回。据我所知,这就是我们需要做的全部内容;然而,经过多次运行1000个线程,我发现电话簿中的条目数量不一致,且随处可见:
Phone Book Entries: 856
Phone Book Entries: 901
Phone Book Entries: 876
Phone Book Entries: 902
Phone Book Entries: 912
有人能帮我弄清楚发生了什么吗?我的锁定代码有问题还是更糟糕的是我的测试构造有问题?我对这个多线程问题空间非常陌生,谢谢!
sleep(10)
存在竞争条件。在需要等待所有工作完成的实际应用中,你不会使用它。尝试将其改为sleep(30)
作为实验,看看结果是否会有所改善。 - Phillip Mills[Person]
的数组,然后有一些方法可以根据给定的电话号码或姓名返回人员数组(例如使用filter
)。我们中的许多人都有多个带有相同电话号码的联系人(例如共享业务线或甚至拥有相同家庭电话的家庭)。我甚至有几个不同的人用同一个名字(例如,我有两个真正不同的“Paul Williams”)。你想怎么做就怎么做吧,这只是我的一点想法。 - Rob