Home / Брави, конец за безбедност, и Свифт

Брави, конец за безбедност, и Свифт

glider-thumbМајк Аш

А програмерот во Разумното Лабс од ноќта,
и пилот на едрилица по ден
Економист од Универзитетот во Висконсин-Милвоки и на Универзитетот на Слоновата Орлеан

Оригиналното: Friday Q&A 2015-02-06: Locks, Thread Safety, and Swift

 

Интересен аспект на Свифт е дека нема ништо на јазикот се однесуваат на нишките, а поконкретно mutexes / брави. Дури Objective-C @synchronized и атомски својства. За среќа, повеќето од моќта е во API-платформа кои се лесно да се користи од Свифт. Денес јас одам да се истражуваат на употребата на овие API и преминот од Objective-C, тема е предложено од страна на Камерон Pulsford.

Брзо повториме на Брави
А брава, или mutex, е конструкција која гарантира само една нишка е активен во даден регион на кодот во секое време. Тие обично се користат за да се осигура дека повеќе теми пристап на променливи податоци структура види сето конзистентен поглед на тоа. Постојат неколку видови на брави:

  1. Блокирање брави спијам конец, додека тоа го чека уште една нишка за да ја ослободите. Ова е вообичаен однесување.
  2. Spinlocks користат напорен циклус постојано да се провери да се види дали заклучување не беше објавен. Ова е поефикасна ако чекање е ретка, но време отпад процесорот ако чекање е честа појава.
  3. За читање / запишување брави овозможи повеќе “читателот” теми за да влезат во една област во исто време, но исклучува сите други теми (вклучувајќи читателите), кога “писател” нишка стекнува за заклучување. Ова може да биде корисно како многу структури на податоци се безбедни за да го прочитате од повеќе теми истовремено, но небезбедно да се напише, додека други теми се или читање или пишување.
  4. Рекурзивен брави овозможи една нишка да се здобијат со иста заклучување повеќе пати. Не-рекурзивен брави може ќор-сокак, несреќа, или на друг начин да згрозениот се нефер, кога повторно влезе од истата тема.

API-
API-јата на Apple имаат еден куп на различни mutex објекти. Ова е долг, но не и исцрпна листа:

  1. pthread_mutex_t
  2. pthread_rwlock_t
  3. dispatch_queue_t
  4. NSOperationQueue кога конфигуриран да биде сериски.
  5. NSLock
  6. OSSpinLock

Во прилог на ова, Objective-C @synchronized јазик конструкт што во моментот се спроведува на врвот наpthread_mutex_t За разлика од @synchronized не користи експлицитно заклучување предмет, туку го третира произволна Objective-C објектот како да станува збор за заклучување.@synchronized(someObject) дел ќе го блокира пристапот до било која @synchronized делови кои го користат истиот објект покажувач. Овие различни објекти имаат различни однесувања и способности:

  1. pthread_mutex_t е блокирање брава која евентуално може да се конфигурира како рекурзивен заклучување.
  2. pthread_rwlock_t е блокирање на бравата за читање / запишување.
  3. dispatch_queue_t може да се користи како заклучување блокирање. Тоа може да се користи како брава за читање / запишување за конфигурирање на тоа како истовремени редица и употреба на бариера блокови. Таа, исто така поддржува асинхрони извршување на заклучени регионот.
  4. NSOperationQueue може да се користи како заклучување блокирање. Како dispatch_queue_t ја поддржува асинхрони извршување на заклучени регионот.
  5. NSLock е блокирање заклучување како Objective-C класа. Нејзиниот придружник класаNSRecursiveLock е рекурзивен заклучување, како што самото име укажува.
  6. OSSpinLock е spinlock, како што името укажува.

@synchronized е блокирање рекурзивен заклучување.

Видови вредност
Имајте на ум дека pthread_mutex_t pthread_rwlock_t и OSSpinLock се видови вредност, не се референтни типови. Тоа значи дека ако користите = на нив, може да направи копија. Ова е важно, бидејќи овие видови не може да бидат копирани! Ако ја копирате еден од pthread типови, копија ќе бидат неупотребливи и може да паѓа кога ќе се обидат да го користат. На pthread функции кои работат со овие типови се претпостави дека вредностите се во исто мемориски адреси, како, каде што беа иницијализиран, да ги става на некое друго потоа е лоша идеја. OSSpinLock не ќе несреќа, но можете да добиете сосема посебен брава надвор од тоа што никогаш не е она што го сакате.

Ако ги користат овие типови, мора да бидете внимателни да не да ги ископирате, без разлика дали со експлицитно = оператор, или имплицитно од страна, на пример, нивно вградување во struct или фаќањето на затворање.

Покрај тоа, бидејќи брави се инхерентно променливи објекти, тоа значи дека треба да ги декларираат соvar наместо let

Другите се референтните типови, што значи дека може да се раздавани наоколу по волја, и може да се прогласи со let

Иницијализација
Ажурирање 2015/02/10: проблемите опишани во овој дел се направени застарен и со неверојатна брзина. Епл објави Xcode 6.3b1 вчера кој вклучува Свифт 1.2. Меѓу другите промени, Ц structs сега се увезуваат со празен initializer што ги поставува сите области на нула. На кратко, сега можете да го напишете `pthread_mutex_t ()` без екстензии ќе разговараат подолу. Овој дел ќе остане за историски интерес, но повеќе не е во врска со јазикот.

На pthread видови се проблематични да го користите од Свифт. Тие се дефинирани како матна struct а со еден куп на складирање, како што се:

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

Намерата е дека ќе ги прогласи, а потоа ги иницијализира со користење на init функција која го покажувачот на чување и да го пополнува. Во C, како изгледа:

    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);

Оваа работи добро, се додека вие не заборавајте да се јавите pthread_mutex_init Сепак, Свифт, навистина, навистина не и се допаѓа деиницијализира променливи. Еквивалент Свифт не успева да се состави:

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

Свифт бара променливи да биде иницијализиран пред тие да се користи. pthread_mutex_init не ја користи вредноста на променливата помина во, тоа само го презапишува, но брзо не знае дека и така го произведува грешка. За задоволување на компајлерот, променливата треба да се иницијализира со нешто, но тоа е потешко отколку што изгледа. Користење () по типот не работи:

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

Свифт бара вредности за оние нетранспарентно области. __sig Е лесно, ние само може да помине нула.__opaque Е малку повеќе досадни. Еве како таа се добива премостена во Свифт:

    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)
    }

Нема лесен начин да се добие една голема торка полн со нули, па мора да се има сè напише:

    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))

Ова е страшно, но не можев да се најде добар начин околу неа. Најдоброто што можев да направам е да ја заврши во продолжување, така што на празните () работи. Тука се две екстензии сум направил:

    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)
        }
    }

Со овие наставки, ова работи:

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

Тоа може да биде можно да се тркалаат на повикот да pthread_mutex_init во продолжувањето initializer како и, но не постои гаранција дека self во struct init поени на променлива се иницијализира. Бидејќи овие вредности не може да се пресели во меморијата откако беше иницијализиран, сакав да се задржи на иницијализација како посебен повик.

Заклучување Омоти
Да се направат овие различни API, полесно да се користи, напишав една серија на мали функции обвивка.Јас се населиле на with како што е инспириран удобен, кратко, синтакса изглед име од страна Пајтонwith изјава. Преоптоварувањето на функциите Свифт овозможува користење на истото име за сите овие различни видови. Основната форма изгледа вака:

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

Ова тогаш извршува f со брава одржа. Ајде да се имплементира за сите овие видови.

За типови на вредноста, тоа треба да се земе покажувач на бравата па заклучување / отклучување на функции може да го менувате. Имплементацијата за pthread_mutex_t само го нарекува соодветни заклучување и отклучување функции, со повик до f помеѓу:

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

Имплементацијата за pthread_rwlock_t е речиси идентична:

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

Направив придружник на овој оној кој ги зема брава пишуваат, кој повторно изгледа многу исти:

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

Оној за dispatch_queue_t е уште поедноставно. Тоа е само обвивка околу dispatch_sync

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

Всушност, ако некој требаше премногу паметен за сопствено добро и сакаше да ги збунуваат луѓето, може да ги искористат предностите на функционална природа на Свифт и едноставно да се напише:

    let with = dispatch_sync

Ова е паметно за неколку различни причини, меѓу кои е дека тој мензи со преоптоварување тип-базирани ние се обидуваме да се користи тука.

NSOperationQueue е концептуално слична, но нема директен еквивалент на dispatch_sync Наместо тоа, ние се создаде една работа, да го додадете на листа на чекање, и експлицитно се чека тој да заврши:

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

Имплементацијата за NSLock личи на pthread верзии, само со малку различни заклучување повици:

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

Конечно, OSSpinLock имплементација е повторно повеќе од истата:

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

Имитирачкиsynchronized
Со овие омоти, имитирање на основите @synchronized е прилично едноставна. Додадете имот за својата класа, која го има брава, тогаш се користи with каде што би ги @synchronized пред:

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

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

Добивање на податоци од блок е малку помалку пријатно, за жал. @synchronized ви овозможува даreturn од внатре, и тоа не функционира со with Наместо тоа, ќе мора да се користи var и го назначи за тоа во рамките на блокот:

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

Треба да биде можно да се заврши на boilerplate во генеричка функција, но имав проблеми со добивање тип инференција Свифт компајлерот да играат заедно и немаат решение само уште.

Имитирајќи атомска Својства
Атомски својства не се често корисни. Проблемот е во тоа што, за разлика од многу други корисни својства на кодот, atomicity не компонира. На пример, ако функцијата f не излегуваат во јавноста меморија, како и функцијата g не излегуваат во јавноста меморија, тогаш функцијата h дека само го нарекува f и g исто така, не излегуваат во јавноста меморија. Истото не важи и atomicity. На пример, замислете имате сет на атомски, нишка безбедно Account класи:

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

Сега ќе се движат на пари за да се заштеди:

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

Во друга тема, можете вкупно до рамнотежа и кажете на корисникот:

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

Ако ова тече само во погрешно време, тоа ќе печати нула наместо 100, и покрај фактот дека на Accountобјекти самите се целосно атомски и корисникот имаше биланс 100 цело време. Поради ова, тоа е обично подобро да се изгради целата потсистеми да биде атомски, наместо поединечни својства.

Постојат ретки случаи каде атомски својства се корисни, бидејќи тоа навистина е самостојна работа што само треба да се нишка безбедно. За да се постигне тоа во Свифт, потребен ви е компјутеризирана имотот што ќе се направи за заклучување, и втор нормално имотот што, всушност, ќе се одржи на вредност:

    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
            }
        }
    }

Изборот на вашиот заклучување на API
На pthread API-јата може да биде намалена веднаш поради тешкотијата на користење на нив од Свифт, како и фактот дека тие не се направи нешто што другите не се, исто така, на API-јата се направи. Јас често сакале да ги користите во C и Objective-C, бидејќи тие се прилично едноставно и брзо, но тоа не е достоен за тоа тука, освен ако нешто навистина го бара тоа.

За читање / запишување брави најчесто не вреди да се грижиш. За заеднички случаи, каде што го чита и пишува се брзо, екстра надземни се користи од страна на заклучување за читање / запишување надминува способноста да имаат повеќе истовремени читателите.

Рекурзивен брави се претежно покана до ќор-сокак. Има случаи кога тие се корисни, но ако се најдете со дизајн, каде што треба да се преземат за заклучување, која веќе е заклучен на тековната нишка, што е добар знак, веројатно ќе треба да го преиспита, така што не е потребно.

Мое мислење е дека, кога се двоумите, стандардно да dispatch_queue_t Тие се повеќе во тешка категорија, но ова ретко е важно. API е релативно лесен, и тие се обезбеди што никогаш не заборавајте да го спарите повик брава со отклучување на повик. Тие се обезбеди еден тон убаво објекти кои може да дојде во рака, како и способноста да се користи еден dispatch_async повик да се кандидира заклучени код во позадина, или способност да се постави тајмери или други извори настан насочени директно на дното, така што тие автоматски изврши заклучена. Можете дури и може да го користи како цел за нешта како NSNotificationCenter набљудувачи и NSURLSession делегати со употреба на underlyingQueueсопственост на NSOperationQueue нови во OS X 10,10 и iOS 8.

NSOperationQueue сака тоа би можело да биде како кул што dispatch_queue_t и има малку или воопшто немаат причини да го користите како API, заклучување. Тоа е повеќе незгодни да се користи и не обезбедува никакви предности за типична употреба како заклучување на API, иако раководството на автоматско зависност за операции понекогаш може да биде корисно во други контексти.

NSLock е едноставен заклучување класа која е лесна за употреба и разумно брз. Тоа е добар избор ако сакате експлицитна заклучување и отклучување на повици поради некоја причина, а не на API на блокови со седиште dispatch_queue_t но има малку причини да се користи во повеќето случаи.

OSSpinLock е одличен избор за употреба каде бравата е донесена често, тврдењето е ниска, а заклучени код тече брзо. Таа има многу пониски надземни и ова им помага на претставата за топла код патеки. Од друга страна, тоа е лош избор за употреба каде што може да се одржи кодот за заклучување за значителен износ на време, или несогласување е честа појава, како тоа ќе се губи време на процесорот. Во принцип, се стандардно dispatch_queue_t но имајте OSSpinLock во умот, како прилично лесна оптимизација ако почне да се појавиш во профили.

Заклучок
Свифт нема јазик објекти за конец синхронизација, но овој недостаток е повеќе од доволно за богатството на заклучување на API-јата на располагање во рамките на Apple. НОД и dispatch_queue_t се уште се ремек-дело и API, работи одлично во Свифт. @synchronized или атомски својства, но имаме работи кои се подобри.

Тоа е тоа за денес. Се врати следниот пат за нови возбудливи авантури. Петок Q & A е изграден на предлози тема на читателите како тебе, па ако имаш нешто што би сакал да видам покриени овде, ве молиме да го испратите во!

Дали ќе уживате во овој напис? Јас сум продажба на цела книга полна со нив. Тоа е на располагање за iBooks и поттикне, плус директно преземање во PDF и ePub формат. Тоа е исто така на располагање во хартија за старомодни.
banner