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) Lock-եր: Թույլատրում են միաժամանակ մի քանի հոսքերին կարդալ ռեսուրսը, բայց միայն մեկին գրել:
    • 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()
  • Ատոմարային (Atomic) օպերացիաներ: Գործողություններ, որոնք կատարվում են ամբողջությամբ առանց ընդհատումների այլ հոսքից:
    • 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): Շինվածքների կամ ցուցակների պատճենների փոխանցումը կարող է օգնել խուսափել race condition-ներից, եթե տվյալները չեն բաժանվում ուղղակիորեն հոսքերի միջև փոփոխվող տեսքով:

Ընտրությունը կախված է առաջադրանքի բնույթից և անհրաժեշտ պարալելիզմի մակարդակից:

Ընդհանուր առմամբ, iOS-ում DispatchQueue (սերիական և համաժամանակյա բարիերներով) օգտագործելը առավել հարմար է, քանի որ հեշտ է օգտագործել և լավ ինտեգրված է Grand Central Dispatch (GCD)-ի հետ: