Brave, Thread Sigurnost i Swift

glider-thumbMike Ash
A programmer at Plausible Labs by night,
and a glider pilot by day
Alumnus of the University of Wisconsin-Milwaukee and the Université d’Orléans
Zanimljiv aspekt Swift je da ne postoji ništa na jeziku koji se odnose na Threading, i još posebno na Mutexe / brave. Čak i Objective-C @synchronized i atomske svojstva. Srećom, većina snaga je u API-platformu koja se lako koristi od Swift. Danas ću istražiti korištenje tih API i prelazak iz Objective-C, a tema je predložio Cameron Pulsford.

Kratkog pregleda na Brave
A lock, ili mutex, je konstrukt koji osigurava samo jedna nit je aktivan u određenom području koda u bilo kojem trenutku. Oni su obično koristi kako bi se osiguralo da se više niti pristupanje promjenljivi strukturu podataka sve vidjeti konzistentan pogled na to. Postoji nekoliko vrsta brava:

  1. Blokiranje brave spavati nit dok čeka još jedan nit da biste ga oslobodili. Ovo je uobičajeno ponašanje.
  2. Spinlocks koristiti zauzet petlju da stalno provjeravati da li je pušten bravu. Ovo je efikasnije ako čekanja je rijedak, ali vrijeme otpad CPU ako čekanja je uobičajena.
  3. Čitač / pisac brave omogućiti više “Čitač” niti da uđe u regiji istovremeno, ali isključuje sve ostale teme (uključujući i čitaoce) kada je “pisac” nit stiče bravu. Ovo može biti korisno kao strukture mnogi podaci su sigurni da pročitate od više niti istovremeno, ali nesiguran pisati dok su druge teme su ili čitanje ili pisanje.
  4. Rekurzivna brave dopustiti jednu nit da stekne isti zaključavanja više puta. Non-rekurzivni brave može zastoja, sudar, ili na neki drugi način loše ponaša kada ponovno ušao iz iste nit.

API
Appleov API imaju gomilu različitih mutex objekata. Ovo je dugačak, ali ne i konačan popis:

  1. pthread_mutex_t
  2. pthread_rwlock_t
  3. dispatch_queue_t
  4. NSOperationQueue kada je podešeno da se serijski.
  5. NSLock
  6. OSSpinLock

Pored toga, Objective-C @synchronized jezik konstrukt, koji u ovom trenutku se sprovodi na vrhupthread_mutex_t Za razliku od @synchronized ne koristi eksplicitna bravu objekt, nego tretira proizvoljan Objective-C objekt kao da je bravu. @synchronized(someObject) dio će blokirati pristup na bilo koju@synchronized sekcije koje koriste isti objekat pokazivač. Ovi različiti sadržaji svi imaju različite ponašanje i sposobnosti:

  1. pthread_mutex_t je blokira bravu koja može opcionalno biti konfiguriran kao rekurzivna bravu.
  2. pthread_rwlock_t je blokira čitač / pisač bravu.
  3. dispatch_queue_t se može koristiti kao blokiranje bravu. Može se koristiti kao čitač / pisač lock konfiguriranjem kao istovremeni red i pomoću barijera blokova. On također podržava asinkroni izvršenje zaključan regije.
  4. NSOperationQueue se može koristiti kao blokiranje bravu. Kao i dispatch_queue_t podržava asinkroni izvršenje zaključan regije.
  5. NSLock blokira lock kao Objective-C klase. Njegov pratilac razred NSRecursiveLock je rekurzivna bravu, kao što samo ime govori.
  6. OSSpinLock je Spinlock, kao što ime ukazuje.

@synchronized je rekurzivna blokira bravu.

Tipovi vrijednost
Imajte na umu da pthread_mutex_t pthread_rwlock_t a OSSpinLock su tipovi vrijednosti, a ne referentne vrste. To znači da ako koristite = na njima, napravite kopiju. Ovo je važno, jer se ovi tipovi ne mogu se kopirati!Ako kopirate jednu od pthread tipova, kopija će biti neupotrebljive i može srušiti kada pokušate da ga koristite.U pthread funkcije koje rade s ove vrste pretpostaviti da su vrijednosti na isti memorijske adrese kao gdje su inicijalizira, i stavljajući ih negdje drugdje nakon toga je loša ideja. OSSpinLock neće srušiti, ali ti potpuno odvojena od bravu to što je nikad nije ono što želite.

Ako koristite ove vrste, morate biti oprezni da nikada ne ih kopirati, bez obzira da li eksplicitno s = operaterom, ili implicitno, na primjer, ugrađivanje ih u struct ili hvatanje ih u zatvaranje.

Osim toga, s obzirom da su brave inherentno promjenjiv objekata, to znači da treba da ih proglasi sa var umjestolet

Ostali su referentne vrste, što znači da se može prenijeti oko po volji, a može biti proglašen sa let

Inicijalizacija
Update 2015-02-10: problemi opisani u ovom poglavlju su učinjeni zastario s prekrasnim brzinom. Apple jučer koji uključuje Swift 1.2 pušten Xcode 6.3b1. Između ostalih promjena, C Strukture su sada uvezeni sa praznim inicijalizator koji postavlja sva polja na nulu. Ukratko, sada možete pisati `pthread_mutex_t ()` bez produžetaka sam razgovarati ispod. Ovo poglavlje će ostati za istorijskog značaja, ali više nije relevantna za jezik.

U pthread tipovi su problematični za korištenje od Swift. Oni definiran kao neprozirne struct e sa gomilom pohranu, kao što su:

  struct _opaque_pthread_mutex_t {
        long __sig;
        char __opaque[__PTHREAD_MUTEX_SIZE__];
    };

Namjera je da ih proglasi, onda inicijalizovati ih koristeći init funkciju koja uzima pokazivač na čuvanje i ispunjava ga. U C, to izgleda ovako:

    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);

Ovo radi dobro, sve dok se sjećate da pozovete pthread_mutex_init Međutim, Swift stvarno, stvarno ne sviđa neinicijalizovanih varijable. Ekvivalent Swift ne sastavlja:

    var mutex: pthread_mutex_t
    pthread_mutex_init(&mutex, nil)
    // error: address of variable 'mutex' taken before it is initialized

Swift zahtijeva varijable treba inicijalizirati prije nego su navikli. pthread_mutex_init ne koristi vrijednost varijable prošao u, to samo prepisuje, ali Swift to ne zna i tako proizvodi grešku. Da bi zadovoljili kompajler, varijabla mora biti inicijalizovan nešto, ali to je teže nego što izgleda. Koristeći () nakon što je tip ne radi:

    var mutex = pthread_mutex_t()
    // error: missing argument for parameter '__sig' in call

Swift zahtijeva vrijednosti za one neprozirne polja. __sig Je lako, možemo samo proći nula. __opaque Je malo neugodno. Evo kako to biva u premostiti Swift:

  struct _opaque_pthread_mutex_t {
        var __sig: Int
        var __opaque: (Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8,
                       Int8, Int8, Int8, Int8)
    }

Nema jednostavan način da biste dobili veliki tuple pun nula, tako da morate napisati sve to:

   var mutex = pthread_mutex_t(__sig: 0,
                             __opaque: (0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0))

Ovo je strašno, ali nisam mogao naći dobar način oko nje. Najbolje što sam mogao učiniti je umotati se u produžetak, tako da je prazan () radi. Ovdje su dva proširenja sam napravio:

    extension pthread_mutex_t {
        init() {
            __sig = 0
            __opaque = (0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0)
        }
    }

    extension pthread_rwlock_t {
        init() {
            __sig = 0
            __opaque = (0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0)
        }
    }

Sa ovim ekstenzije, ovo radi:

    var mutex = pthread_mutex_t()
    pthread_mutex_init(&mutex, nil)

To može biti moguće roll poziv pthread_mutex_init u produžetak inicijalizator kao i, ali nema garancije daself u struct init ukazuje na varijablu koja se inicijalizira. Od ove vrijednosti ne mogu se preselio u memoriji nakon što inicijalizovani, želeo sam da inicijalizacije kao poseban poziv.

Locking zavijanje
Da bi ovih različitih API lakši za korištenje, napisao sam niz malih funkcija omot. Naselili sam na with kao zgodan, kratak, ime sintaksa izgleda inspiriran Python with izjavom. Swift funkcija preopterećenja omogućava da koristite isti naziv za sve ove različite vrste. Osnovni oblik izgleda ovako:

    func with(lock: SomeLockType, f: Void -> Void) { ...

Ovaj zatim izvršava f s bravu održan. Hajde da sprovede to za sve ove vrste.

Za tipove vrijednosti, to treba uzeti pokazivač na bravu tako zaključavanje / otključavanje funkcije može mijenjati. Implementacija za pthread_mutex_t samo poziva odgovarajuću bravu i otključali funkcije, uz poziv na f između:

   func with(mutex: UnsafeMutablePointer<pthread_mutex_t>, f: Void -> Void) {
        pthread_mutex_lock(mutex)
        f()
        pthread_mutex_unlock(mutex)
    }

Implementacija za pthread_rwlock_t je gotovo identična:

   func with(rwlock: UnsafeMutablePointer<pthread_rwlock_t>, f: Void -> Void) {
        pthread_rwlock_rdlock(rwlock)
        f()
        pthread_rwlock_unlock(rwlock)
    }

Napravio sam pratilac ovo da traje lock, što opet izgleda mnogo isti:

  func with_write(rwlock: UnsafeMutablePointer<pthread_rwlock_t>, f: Void -> Void) {
        pthread_rwlock_wrlock(rwlock)
        f()
        pthread_rwlock_unlock(rwlock)
    }

Onaj za dispatch_queue_t je još jednostavniji. To je samo omotač oko dispatch_sync

    func with(queue: dispatch_queue_t, f: Void -> Void) {
        dispatch_sync(queue, f)
    }

U stvari, ako su bili previše pametan za svoje vlastito dobro i da želi da zbuni ljude, moglo bi se iskoristiti funkcionalne prirode Swift i jednostavno napišite:

   let with = dispatch_sync

Ovo je mudro za nekoliko razloga, od kojih kao da se zeza sa preopterećenje tip-based pokušavamo da koriste tu najmanje.

NSOperationQueue je konceptualno slična, ali nema direktan ekvivalent dispatch_sync Umjesto toga, mi stvaramo operaciju, dodajte ga u red, i eksplicitno čekati da se završi:

    func with(opQ: NSOperationQueue, f: Void -> Void) {
        let op = NSBlockOperation(f)
        opQ.addOperation(op)
        op.waitUntilFinished()
    }

Implementacija za NSLock izgleda kao pthread verzije, samo s malo drugačiji pozivima zaključavanje:

  func with(lock: NSLock, f: Void -> Void) {
        lock.lock()
        f()
        lock.unlock()
    }

Konačno, OSSpinLock implementacija je opet više od istog:

   func with(spinlock: UnsafeMutablePointer<OSSpinLock>, f: Void -> Void) {
        OSSpinLockLock(spinlock)
        f()
        OSSpinLockUnlock(spinlock)
    }

Imitatorsynchronized
Sa ovim omoti, imitirajući @synchronized je prilično jednostavan. Dodajte oglas u tvom razredu da drži zaključavanje, onda koristite with kojima se @synchronized prije:

   let queue = dispatch_queue_create("com.example.myqueue", nil)

    func setEntryForKey(key: Key, entry: Entry) {
        with(queue) {
            entries[key] = entry
        }
    }

Dobivanje podataka iz bloka je nešto manje prijatan, nažalost. @synchronized vam omogućava da returniznutra, da ne radi sa with Umjesto toga, morate koristiti var i dodijeliti ga unutar bloka:

    func entryForKey(key: Key) -> Entry? {
        var result: Entry?
        with(queue) {
            result = entries[key]
        }
        return result
    }

To bi trebalo biti moguće završiti opštenamenske u generički funkciji, ali ja sam imao problema da je Swift kompajler vrstu zaključak da igraju zajedno i nemaju rješenje samo još.

Imitiranje Atomic Properties
Atomske osobine nisu često korisne. Problem je u tome, za razliku od mnogih drugih korisnih osobina kod, atomicity ne izrađuje. Na primjer, ako funkcija f ne curi memorije, a funkcija g ne curi memorije, onda funkcijah da samo zove f i g i ne curi memorije. Isto ne važi za atomicity. Na primjer, zamislite da imate set atomske, nit-sef Account klase:

   let checkingAccount = Account(amount: 100)
    let savingsAccount = Account(amount: 0)

Sada se krećete novac za uštede:

  checkingAccount.withDraw(100)
    savingsAccount.deposit(100)

U drugom nit, vi se ukupno stanje i reći korisnika:

  println("Your total balance is: \(checkingAccount.amount + savingsAccount.amount)")

Ako se to radi na samo pogrešno vrijeme, to će ispisati nula umjesto 100, uprkos činjenici da je Account objekti sami su u potpunosti atomske i korisnik ima balans 100 cijelo vrijeme. Zbog toga, to je obično bolje izgraditi cijelu podsistema da bude atomski, a ne pojedinac svojstva.

Postoje rijetki slučajevi u kojima atomske osobine su korisni, jer stvarno je samostalan stvar koja samo treba da se konac siguran. Da bi se postigao da je u Swift, potreban vam je izračunatog imovine koja će raditi za zaključavanje, a drugi normalan imovine koja će zapravo držati vrijednost:

    private let queue = dispatch_queue_create("...", nil)
    private var _myPropertyStorage: SomeType

    var myProperty: SomeType {
        get {
            var result: SomeType?
            with(queue) {
                result = _myPropertyStorage
            }
            return result!
        }
        set {
            with(queue) {
                _myPropertyStorage = newValue
            }
        }
    }

Izbor za zaključavanje API
U pthread API može se diskontiraju odmah zbog teškoća njihovog korištenja od Swift, kao i činjenica da oni ne rade ništa da drugi API ne i raditi. Često volim da ih koriste u C i Objective-C, jer oni su prilično jednostavan i brz, ali to ne vrijedi to ovdje, osim ako se nešto stvarno zahtijeva to.

Čitač / pisac brave su uglavnom ne vrijedi brinuti. Za zajednički slučajeve, gdje čita i piše su brzi, dodatne iznad glave koju koristi za zaključavanje čitač / pisač nadmašuje sposobnost da imaju više istovremenih čitalaca.

Rekurzivne brave su uglavnom poziv na zastoja. Postoje slučajevi u kojima su korisni, ali ako se nađete s dizajnom, gdje vam je potrebno da se bravu koja je već zaključan na trenutnu nit, to je dobar znak da bi trebalo da razmislimo tako da to nije potrebno.

Moje mišljenje je da je, kada je u nedoumici, default da dispatch_queue_t Oni su još u teškoj kategoriji, ali to rijetko važno. API je razumno zgodan, i oni su sigurni da nikada zaboraviti da uparite bravu poziv za otključavanje poziv. Oni pružaju tonu lijepih objekata koji mogu doći u ruci, kao što je sposobnost da koristi jednu dispatch_async poziv za pokretanje zaključani kod u pozadini, ili mogućnost za postavljanje tajmera ili drugih izvora događaj ciljane direktno na red, tako da su automatski izvršiti zaključana. Možete čak koristiti ga kao meta za stvari kao što su NSNotificationCenter posmatrača i NSURLSession delegata pomoćuunderlyingQueue imovine NSOperationQueue novo u OS X i iOS 10.10 8.

NSOperationQueue želi to bi mogao biti kao kul kao dispatch_queue_t i ima malo ili nimalo razloga da ga koriste kao bravu API. To je više nezgrapan za korištenje i ne daje nikakve prednosti za tipičnu upotrebu kao zaključavanje API, iako je automatsko upravljanje zavisnosti za operacije ponekad može biti koristan u drugim kontekstima.

NSLock je jednostavna klasa za zaključavanje koji je jednostavan za korištenje i razumno brzo. To je dobar izbor ako želite eksplicitne zaključavanje i otključavanje pozive iz nekog razloga, a ne blokira-based APIdispatch_queue_t ali tu je malo razloga da ga koriste u većini slučajeva.

OSSpinLock je odličan izbor za upotrebu u kojoj se uzima često zaključavanje, tvrdnja je nizak, a zaključani kod radi brzo. Ona ima mnogo niže iznad glave i to pomaže performanse za vruće kod staze. S druge strane, to je loš izbor za upotrebu u kojima kod mogu imati bravu za znatnu količinu vremena, ili tvrdnja je zajednička, jer će gubiti vrijeme CPU. U principu, prema zadanim postavkama dispatch_queue_t ali imajte OSSpinLock na umu kao prilično lako optimizaciju ako počne da se pojavi u profiler.

Zaključak
Swift nema jezika postrojenja za sinhronizaciju nit, ali ovaj nedostatak je više nego su se za bogatstvo zaključavanje API dostupan u Apple-ov okvirima. GCD i dispatch_queue_t su i dalje remek i API radi odlično u Swift. Mi @synchronized ili atomske svojstva, ali mi imamo stvari koje su bolje.

To je to za danas. Vrati se sljedeći put za više uzbudljive avanture. Petak Pitanja i izgrađen je na sugestije temu čitatelja poput tebe, pa ako imate nešto što bih da vidim ovdje pokrivene, molimo vas da ga pošaljete u!

Jeste li uživati ovaj članak? Ja prodajem čitavu knjigu puna njih. To je dostupan za iBooks i Kindle, plus direktni download u PDF i ePub formatu. To je također dostupan u radu za staromodne