2015-02-18 6 views
3

При написании инструмента командной строки (CLT) в Swift я хочу обработать много данных. Я решил, что мой код связан с процессором, и производительность может выиграть от использования нескольких ядер. Поэтому я хочу распараллелить части кода. Скажем, я хочу, чтобы достичь следующего псевдокода:Несколько работников в инструменте командной строки Swift

Fetch items from database 
Divide items in X chunks 
Process chunks in parallel 
Wait for chunks to finish 
Do some other processing (single-thread) 

Теперь я использую НОД и наивный подход будет выглядеть следующим образом:

let group = dispatch_group_create() 
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 
for chunk in chunks { 
    dispatch_group_async(group, queue) { 
     worker(chunk) 
    } 
} 
dispatch_group_wait(group, DISPATCH_TIME_FOREVER) 

Однако НОД требует цикла выполнения, так код будет зависать, поскольку группа никогда не выполняется. Runloop можно запустить с dispatch_main(), но он никогда не выходит. Также можно запустить NSRunLoop всего несколько секунд, однако это не похоже на твердое решение. Независимо от GCD, как это можно достичь с помощью Swift?

+0

GCD не требует цикла запуска, но ваш код может отправлять блоки в основной поток, и в этом случае вам нужно либо вызвать 'dispatch_main', либо использовать цикл выполнения. – CouchDeveloper

+0

@CouchDeveloper без цикла запуска блоки, представленные в основной поток, не будут работать правильно? Для этого требуется цикл выполнения, даже 'dispatch_main' также создает цикл запуска под капотом. – bouke

+0

'dispatch_main' необязательно создавать цикл запуска. Я действительно верю, что этого не произойдет. Это один из подходов к выполнению блоков, отправленных в основную очередь. И да, он никогда не возвращается, что, вероятно, не имеет большого значения во многих приложениях. Однако я считаю, что если вы не отправляете блоки в основной поток, приложение должно работать нормально без dispatch_main и без цикла запуска (используйте dispatch_groups, чтобы дождаться завершения). – CouchDeveloper

ответ

8

я ошибочно интерпретирован фиксирующая нить для программы висящей. Работа будет выполняться просто отлично без цикла запуска. Код в вопросе будет работать нормально и блокирует основной поток до тех пор, пока вся группа не закончит.

Так говорят chunks содержит 4 элемента рабочей нагрузки, следующий код раскручивает 4 одновременных рабочих, а затем ждет всех рабочих, чтобы закончить:

let group = dispatch_group_create() 
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 
for chunk in chunks { 
    dispatch_group_async(group, queue) { 
     do_work(chunk) 
    } 
} 
dispatch_group_wait(group, DISPATCH_TIME_FOREVER) 
+1

Чтобы помочь будущим читателям, можете ли вы опубликовать исходный код из своего окончательного решения? –

+1

@AaronBrager уверен, я обновил ответ. – bouke

+1

Спасибо за обновление вашего ответа! –

6

Как и с CLI Objective-C, вы можете создать собственный цикл запуска, используя NSRunLoop.

Вот одна из возможных реализаций, созданные по образцу this gist:

class MainProcess { 
    var shouldExit = false 

    func start() { 
     // do your stuff here 
     // set shouldExit to true when you're done 
    } 
} 

println("Hello, World!") 

var runLoop : NSRunLoop 
var process : MainProcess 

autoreleasepool { 
    runLoop = NSRunLoop.currentRunLoop() 
    process = MainProcess() 

    process.start() 

    while (!process.shouldExit && (runLoop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 2)))) { 
     // do nothing 
    } 
} 

Как Мартин отмечает, вы можете использовать NSDate.distantFuture() as NSDate вместо NSDate(timeIntervalSinceNow: 2). (Бросок необходимо, так как distantFuture() метод подписи указывает, что он возвращает AnyObject.)

Если вам необходимо получить доступ аргументы CLI see this answer. Вы также можете return exit codes using exit().

+0

Я не могу не думать, что это очень уродливый/нечитаемый код. Почему бы не вручную манипулировать несколькими потоками? – bouke

+0

Фактически вы можете использовать «отдаленное будущее» вместо двух секунд, сравнить http: // stackoverflow.ком/а/25126900/1187415. 'runMode()' всегда будет возвращаться, если будут обработаны какие-либо источники отправки. –

+0

@bouke Вместо этого вы можете использовать потоки, но я не хочу перефразировать [threads vs. GCD] (http://stackoverflow.com/a/13016973/1445366) здесь. –

2

Я думаю CFRunLoop гораздо проще, чем NSRunLoop в этом случае

func main() { 
    /**** YOUR CODE START **/ 
    let group = dispatch_group_create() 
    let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 
    for chunk in chunks { 
     dispatch_group_async(group, queue) { 
      worker(chunk) 
     } 
    } 
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER) 
    /**** END **/ 
} 


let runloop = CFRunLoopGetCurrent() 
CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode) {() -> Void in 
    dispatch_async(dispatch_queue_create("main", nil)) { 
     main() 
     CFRunLoopStop(runloop) 
    } 
} 
CFRunLoopRun() 
+0

Спасибо за ваш ответ, этот код уже выглядит лучше, чем при использовании 'NSRunLoop'. Однако, если блокировка основного потока прекрасна (это в моем случае), 'dispatch_group_wait' работает просто отлично, я только что обнаружил. – bouke

2

Swift 3 минимальная реализация решения Aaron Brager:

var shouldExit = false 
doSomethingAsync() { _ in 
    defer { 
     shouldExit = true 
    } 
} 
autoreleasepool { 
    var runLoop = RunLoop.current 
    while (!shouldExit && (runLoop.run(mode: .defaultRunLoopMode, before: Date.distantFuture))) {} 
}