Как атомарно увеличить переменную в Swift?
Я хочу иметь возможность увеличивать счетчик атомарно, и я не могу найти ссылку на то, как это сделать.
Добавление дополнительной информации на основе комментариев:
- Вы используете GCD? Я не использую GDC. Необходимость использовать систему очередей для увеличения числа кажется излишним.
- Вы понимаете основную безопасность потока? Да, я бы не стал спрашивать об атомных приращениях.
- Эта переменная является локальной? Нет.
- Это уровень экземпляра? Да, это должно быть частью одного экземпляра.
Я хочу сделать что-то вроде этого:
class Counter {
private var mux Mutex
private (set) value Int
func increment (){
mux.lock()
value += 1
mux.unlock()
}
}
8 ответов
Из API-интерфейсов низкого уровня параллелизма:
Существует длинный список функций OSAtomicIncrement и OSAtomicDecrement, которые позволяют вам увеличивать и уменьшать целочисленные значения атомарным способом - поточно-безопасно, без необходимости блокировки (или использования очередей). Это может быть полезно, если вам нужно увеличить глобальные счетчики из нескольких потоков для статистики. Если все, что вы делаете, это увеличиваете глобальный счетчик, то безошибочные версии OSAtomicIncrement хороши, и, когда нет споров, их дешево вызывать.
Эти функции работают с целыми числами фиксированного размера, вы можете выбрать 32-битный или 64-битный вариант в зависимости от ваших потребностей:
class Counter {
private (set) var value : Int32 = 0
func increment () {
OSAtomicIncrement32(&value)
}
}
(Примечание: как правильно заметил Эрик Эйгнер, OSAtomicIncrement32
и друзья устарели с MacOS 10.12/iOS 10.10. Xcode 8 предлагает использовать функции из <stdatomic.h>
вместо. Однако это кажется трудным, сравните Swift 3: atomic_compare_exchange_strong и https://openradar.appspot.com/27161329. Поэтому следующий подход, основанный на GCD, кажется лучшим решением в настоящее время.)
В качестве альтернативы можно использовать очередь GCD для синхронизации. Из очередей отправки в "Руководстве по программированию параллелизма":
... С помощью очередей отправки можно добавить обе задачи в очередь последовательной отправки, чтобы гарантировать, что только одна задача изменила ресурс в любой момент времени. Этот тип синхронизации на основе очередей более эффективен, чем блокировки, потому что для блокировок всегда требуется дорогостоящая ловушка ядра как в оспариваемых, так и не оспариваемых случаях, тогда как очередь отправки работает в основном в пространстве процессов вашего приложения и вызывает ядро только тогда, когда это абсолютно необходимо.
В вашем случае это было бы
// Swift 2:
class Counter {
private var queue = dispatch_queue_create("your.queue.identifier", DISPATCH_QUEUE_SERIAL)
private (set) var value: Int = 0
func increment() {
dispatch_sync(queue) {
value += 1
}
}
}
// Swift 3:
class Counter {
private var queue = DispatchQueue(label: "your.queue.identifier")
private (set) var value: Int = 0
func increment() {
queue.sync {
value += 1
}
}
}
См. Добавление элементов в массив Swift между несколькими потоками, вызывающими проблемы (поскольку массивы не являются потокобезопасными) - как мне обойти это? или GCD со статическими функциями структуры для более сложных примеров. Этот поток Какие преимущества имеет dispatch_sync перед @synchronized? тоже очень интересно.
Очереди являются избыточным в этом случае. Вы можете использовать DispatchSemaphore
введен в Swift 3 для этой цели так:
import Foundation
public class AtomicInteger {
private let lock = DispatchSemaphore(value: 1)
private var value = 0
// You need to lock on the value when reading it too since
// there are no volatile variables in Swift as of today.
public func get() -> Int {
lock.wait()
defer { lock.signal() }
return value
}
public func set(_ newValue: Int) {
lock.wait()
defer { lock.signal() }
value = newValue
}
public func incrementAndGet() -> Int {
lock.wait()
defer { lock.signal() }
value += 1
return value
}
}
Последняя версия класса доступна здесь.
Я знаю, что этот вопрос уже немного старше, но я недавно наткнулся на ту же проблему. После небольшого исследования и чтения постов, таких как http://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html я придумал это решение для атомного счетчика. Может быть, это также поможет другим.
import Foundation
class AtomicCounter {
private var mutex = pthread_mutex_t()
private var counter: UInt = 0
init() {
pthread_mutex_init(&mutex, nil)
}
deinit {
pthread_mutex_destroy(&mutex)
}
func incrementAndGet() -> UInt {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
counter += 1
return counter
}
}
подробности
xCode 9.1, Swift 4
Решение
import Foundation
class Atomic<T> {
private let semaphore = DispatchSemaphore(value: 1)
private var _value: T
var value: T {
get {
wait()
let result = _value
defer {
signal()
}
return result
}
set (value) {
wait()
_value = value
defer {
signal()
}
}
}
func set(closure: (_ currentValue: T)->(T)){
wait()
_value = closure(_value)
signal()
}
func get(closure: (_ currentValue: T)->()){
wait()
closure(_value)
signal()
}
private func wait() {
semaphore.wait()
}
private func signal() {
semaphore.signal()
}
init (value: T) {
_value = value
}
}
использование
let atomicValue = Atomic(value: 0)
// Single actions with value
atomicValue.value = 0
print("value = \(atomicValue.value)")
// Multioperations with value
atomicValue.set{ (current) -> (Int) in
print("value = \(current)")
return current + 1
}
atomicValue.get{ (current) in
print("value = \(current)")
}
Полный образец
import UIKit
class ViewController: UIViewController {
var atomicValue = Atomic(value: 0)
let dispatchGroup = DispatchGroup()
override func viewDidLoad() {
super.viewDidLoad()
sample()
dispatchGroup.notify(queue: .main) {
print(self.atomicValue.value)
}
}
func sample() {
let closure:(DispatchQueue)->() = { dispatch in
self.atomicValue.set{ (current) -> (Int) in
print("\(dispatch), value = \(current)")
return current + 1
}
// self.atomicValue.get{ (current) in
// print("\(dispatch), value = \(current)")
// }
}
async(dispatch: .main, closure: closure)
async(dispatch: .global(qos: .userInitiated), closure: closure)
async(dispatch: .global(qos: .utility), closure: closure)
}
private func async(dispatch: DispatchQueue, closure: @escaping (DispatchQueue)->()) {
for _ in 0..<10 {
dispatchGroup.enter()
dispatch.async {
closure(dispatch)
self.dispatchGroup.leave()
}
}
}
}
Результаты
Это оказалось наиболее упрощенным решением на момент написания, пока мы не получим правильный атомный тип Swift. Вы можете использовать этот универсальный класс для установки атомарного value
любого вида.
Злоупотребляя DispatchQueue
для этого нет смысла. Во-первых, вы ограничиваетесь блочной синхронизацией, создаете потенциально ненужные замыкания / стековые фреймы, просто для использования внутренних очередей DispatchSemaphore
вместо того, чтобы создавать это напрямую.
final class Atomic<T> {
private let sema = DispatchSemaphore(value: 1)
private var _value: T
init (_ value: T) {
_value = value
}
var value: T {
get {
sema.wait()
defer {
sema.signal()
}
return _value
}
set {
sema.wait()
_value = newValue
sema.signal()
}
}
func swap(_ value: T) -> T {
sema.wait()
let v = _value
_value = value
sema.signal()
return v
}
}
Использование:
let atomicBool = Atomic<Bool>(false)
atomicBool.value = true
let atomicInt = Atomic<Int>(0)
let oldInt = atomicInt.swap(1)
Если вы хотите увеличить Int
s, вот продолжение для этого
extension Atomic where T == Int {
func increment(n: Int) -> Int {
sema.wait()
let v = _value + n
_value = v
sema.signal()
return v
}
}
Вы можете взглянуть на <tcode id="286924"></tcode> библиотека, размещенная Apple, которая поддерживает общие типы
Я улучшил ответ от @florian, используя несколько перегруженных операторов:
import Foundation
class AtomicInt {
private var mutex = pthread_mutex_t()
private var integer: Int = 0
var value : Int {
return integer
}
//MARK: - lifecycle
init(_ value: Int = 0) {
pthread_mutex_init(&mutex, nil)
integer = value
}
deinit {
pthread_mutex_destroy(&mutex)
}
//MARK: - Public API
func increment() {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer += 1
}
func incrementAndGet() -> Int {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer += 1
return integer
}
func decrement() {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer -= 1
}
func decrementAndGet() -> Int {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer -= 1
return integer
}
//MARK: - overloaded operators
static func > (lhs: AtomicInt, rhs: Int) -> Bool {
return lhs.integer > rhs
}
static func < (lhs: AtomicInt, rhs: Int) -> Bool {
return lhs.integer < rhs
}
static func == (lhs: AtomicInt, rhs: Int) -> Bool {
return lhs.integer == rhs
}
static func > (lhs: Int, rhs: AtomicInt) -> Bool {
return lhs > rhs.integer
}
static func < (lhs: Int, rhs: AtomicInt) -> Bool {
return lhs < rhs.integer
}
static func == (lhs: Int, rhs: AtomicInt) -> Bool {
return lhs == rhs.integer
}
func test() {
let atomicInt = AtomicInt(0)
atomicInt.increment()
atomicInt.decrement()
if atomicInt > 10 { print("bigger than 10") }
if atomicInt < 10 { print("smaller than 10") }
if atomicInt == 10 { print("its 10") }
if 10 > atomicInt { print("10 is bigger") }
if 10 < atomicInt { print("10 is smaller") }
if 10 == atomicInt { print("its 10") }
}
}