Sobes.tech
Middle+

Чӣ гуна масъалаи рақобат (race condition) бояд ҳал карда шавад?

sobes.tech AI

Ҷавоб аз AI

Використання синхронізації потоків для доступу до спільних ресурсів.

Основні підходи:

  • Mutexes (Мютекси): Блокують доступ до ресурсу для інших потоків, поки один потік його використовує.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Захоплюємо мютекс
    // Критична секція - безпечний доступ до sharedResource
    sharedResource += 1
    print("Значення ресурсу: \(sharedResource)")
    lock.unlock() // Відпускаємо мютекс
}

// Приклад виклику з різних потоків
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Послідовні черги (Serial Queues): Виконують задачі одну за одною, гарантують, що одночасно лише один блок коду має доступ до ресурсу.
    • DispatchQueue.main
    • DispatchQueue.global() з атрибутом .serial
import Foundation

let serialQueue = DispatchQueue(label: "com.example.serialQueue")
var sharedResource = 0

func modifyResourceAsync() {
    serialQueue.async {
        // Код виконується послідовно
        sharedResource += 1
        print("Значення ресурсу: \(sharedResource)")
    }
}

// Приклад виклику з різних потоків/черг
// modifyResourceAsync()
// modifyResourceAsync()
  • Читаючий-Записуючий (Reader-Writer) Locks: Дозволяють багатьом потокам читати ресурс одночасно, але лише одному писати.
    • DispatchQueue з бар'єрами (.barrier) для запису і синхронним/асинхронним доступом для читання.
import Foundation

let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
var sharedResource = 0

func readResource() {
    concurrentQueue.async { // Читання - багато читачів одночасно
        print("Читаємо ресурс: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Запис - лише один писач і ніхто не читає/пише
        sharedResource = newValue
        print("Пишемо ресурс: \(sharedResource)")
    }
}

// Приклад викликів
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Атомарні операції: Операції, які гарантовано виконуються цілком без переривання іншим потоком. Використовуються для простих типів даних (лічильники, прапорці).
    • OSAtomicIncrement, OSAtomicDecrement (застарілі, але концепція актуальна)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

std::atomic<int> atomicCounter(0);

void incrementCounter() {
    atomicCounter++; // Атомарне інкрементування
    std::cout << "Лічильник: " << atomicCounter << std::endl;
}

// Приклад C++ у контексті iOS (використання з Objective-C++)
// Викликати з різних потоків
  • Типи значень (Value Types): Передача копій структур або перерахувань може допомогти уникнути проблем з гонкою даних, якщо дані не діляться безпосередньо між потоками у змінюваному вигляді.

Вибір методу залежить від специфіки задачі та потрібного рівня паралелізму. Для більшості задач у iOS переважно використовувати DispatchQueue (послідовні та паралельні з бар'єрами) через легкість використання та інтеграцію з Grand Central Dispatch (GCD).