2015-01-07 2 views
2

в приложении, где может выполняться несколько потоков, и не уверены в возможностях, если эти методы будут доступны в среде с несколькими потоками или нет, но чтобы быть в безопасности, я сделал тестовый класс, чтобы продемонстрировать ситуацию.Безопасный доступ к переменной в классе

Один метод has был запрограммирован на то, чтобы быть потокобезопасным (просьба также прокомментировать, если все сделано правильно), но остальное не было.

В такой ситуации, где есть только одна строка кода внутри remove и add, необходимо ли сделать их потокобезопасными или это будет преувеличением.

import Foundation 

class Some {} 

class Test { 
    var dict = [String: Some]() 

    func has(key: String) -> Bool { 
     var has = false 
     dispatch_sync(dispatch_queue_create("has", nil), { [unowned self] in 
      has = self.dict[key] != nil 
     }) 
     return has 
    } 

    func remove(key: String) -> Some { 
     var ob = dict[key] 
     dict[key] = nil 
     return ob 
    } 

    func add(key: String, ob: Some) { 
     dict[key] = ob 
    } 
} 

Edit после комментариев

class Some {} 

class Test { 
    var dict = [String: Some]() 
    private let queue: dispatch_queue_t = dispatch_queue_create("has", DISPATCH_QUEUE_CONCURRENT) 

    func has(key: String) -> Bool { 
     var has = false 
     dispatch_sync(queue) { 
      has = self.dict[key] != nil 
     } 
     return has 
    } 

    func remove(key: String) -> Some? { //returns 
     var removed: Some? 
     dispatch_barrier_sync(queue) { 
      removed = self.dict.removeValueForKey(key) 
     } 
     return removed 
    } 

    func add(key: String, ob: Some) { //not async 
     dispatch_barrier_sync(queue) { 
      self.dict[key] = ob 
     } 
    } 
} 

ответ

6

Как вы проверяете, существует ли ключ неверен. Вы каждый раз создаете новую очередь, а это означает, что операции не происходят синхронно.

Как я хотел бы сделать это, как так:

class Some {} 

class Test { 
    var dict = [String: Some]() 
    private let queue: dispatch_queue_t = dispatch_queue_create("has", DISPATCH_QUEUE_CONCURRENT) 

    func has(key: String) -> Bool { 
     var has = false 
     dispatch_sync(queue) { [weak self] in 
      guard let strongSelf = self else { return } 

      has = strongSelf.dict[key] != nil 
     } 

     return has 
    } 

    func remove(key: String) { 
     dispatch_barrier_async(queue) { [weak self] in 
      guard let strongSelf = self else { return } 

      strongSelf.dict[key] = nil 
     } 
    } 

    func add(key: String, ob: Some) { 
     dispatch_barrier_async(queue) { [weak self] in 
      guard let strongSelf = self else { return } 

      strongSelf.dict[key] = ob 
     } 
    } 
} 

Во-первых, я создаю последовательный очереди, которая будет использоваться для доступа к словарю как свойство объекта, а не создавая новый один каждый раз. Очередь является частной, поскольку она используется только внутри страны.

Когда я хочу получить значение из класса, я просто отправляю блок синхронно в очередь и жду, пока блок закончит работу, прежде чем возвращать, существует ли очередь. Поскольку это не мутирует словарь, безопасно, чтобы несколько блоков этого типа выполнялись в параллельной очереди.

Когда я хочу добавить или удалить значения из словаря, я добавляю блок в очередь, но с барьером. Это означает, что он останавливает все остальные блоки в очереди во время работы. Когда все будет готово, все остальные блоки могут работать одновременно. Я использую асинхронную отправку, потому что мне не нужно ждать возвращаемого значения.

Представьте, что у вас есть несколько потоков, пытаясь определить, существуют ли ключевые значения или добавляются или удаляются значения. Если у вас много чтений, они происходят одновременно, но когда выполняется один из блоков, который изменит словарь, все остальные блоки ожидают, пока это изменение не будет завершено, а затем снова запустится.

Таким образом, вы получаете скорость и удобство одновременного запуска при получении значений и безопасность потока при блокировке во время мутации словаря.

Edited добавить

self отмечен как weak в блоке, так что он не создает опорный цикл. Как упоминал @MartinR в комментариях; возможно, что объект освобождается, пока блоки все еще находятся в очереди. Если это произойдет, то self не определено, и вы, вероятно, получите ошибку времени выполнения, пытающуюся получить доступ к словарю, так как она также может быть освобождена.

Установив объявление self внутри блока, будет слабым, если объект существует, то self не будет нулевым, и может быть условно развернул в strongSelf, что указует на self, а также создает сильную ссылку, так что self не будет освобождаются, пока выполняются инструкции в блоке. Когда эти инструкции будут завершены, strongSelf выйдет за рамки и высвободит сильную ссылку на себя.

Это иногда называют «сильным« я », слабым таинством».

Edited Again: Swift 3 версия

class Some {} 

class Test { 
    var dict = [String: Some]() 
    private let queue = DispatchQueue(label: "has", qos: .default, attributes: .concurrent) 

    func has(key: String) -> Bool { 
     var has = false 
     queue.sync { [weak self] in 
      guard let strongSelf = self else { return } 

      has = strongSelf.dict[key] != nil 
     } 

     return has 
    } 

    func remove(key: String) { 
     queue.async(flags: .barrier) { [weak self] in 
      guard let strongSelf = self else { return } 

      strongSelf.dict[key] = nil 
     } 
    } 

    func add(key: String, ob: Some) { 
     queue.async(flags: .barrier) { [weak self] in 
      guard let strongSelf = self else { return } 

      strongSelf.dict[key] = ob 
     } 
    } 
} 
+1

Отличный ответ! Один вопрос: не было бы «[слабым»] безопаснее? Объект 'Test' может быть освобожден при выполнении барьерного блока. –

+0

@MartinR В этом случае не будет ли освобождена очередь, в которой работает блок? – Abizern

+1

Из [dispatch_barrier_async()] (https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_barrier_async): * «Очередь сохраняемый системой до тех пор, пока блок не завершится ». * - Итак, я думаю, что даже если объект будет освобожден и высвободит свою ссылку на очередь, очередь будет продолжаться до тех пор, пока не будет завершен весь незавершенный блок. –

1

Вот еще скоро 3 решения, которое обеспечивает потокобезопасный доступ к AnyObject.

Он выделяет рекурсивный pthread_mutex, связанный с «объектом», если это необходимо.

class LatencyManager 
{ 
    private var latencies = [String : TimeInterval]() 

    func set(hostName: String, latency: TimeInterval) { 
     synchronizedBlock(lockedObject: latencies as AnyObject) { [weak self] in 
      self?.latencies[hostName] = latency 
     } 
    } 

    /// Provides thread-safe access to given object 
    private func synchronizedBlock(lockedObject: AnyObject, block:() -> Void) { 
     objc_sync_enter(lockedObject) 
     block() 
     objc_sync_exit(lockedObject) 
    } 
} 

Тогда вы можете позвонить, например set(hostName: "stackoverflow.com", latency: 1)

UPDATE

Вы можете просто определить метод в стремительном файле (не в классе):

/// Provides thread-safe access to given object 
public func synchronizedAccess(to object: AnyObject, _ block:() -> Void) 
{ 
    objc_sync_enter(object) 
    block() 
    objc_sync_exit(object) 
} 

И используйте его следующим образом:

synchronizedAccess(to: myObject) { 
    myObject.foo() 
} 
Смежные вопросы