Swift命令行工具中的多个工作人员

时间:2015-02-18 18:05:54

标签: swift

在Swift中编写命令行工具(CLT)时,我想处理大量数据。我已经确定我的代码是CPU限制的,性能可以从使用多个内核中受益。因此,我想并行化部分代码。假设我想要实现以下伪代码:

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

现在我一直在使用GCD,一种天真的方法看起来像这样:

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)

然而,GCD需要一个运行循环,因此代码将在组永远不会执行时挂起。 runloop可以使用dispatch_main()启动,但它永远不会退出。也可以在几秒钟内运行NSRunLoop,但这不是一个可靠的解决方案。无论GCD如何,如何使用Swift实现这一目标?

4 个答案:

答案 0 :(得分:10)

我错误地将锁定线程解释为挂起程序。没有运行循环,工作将执行得很好。问题中的代码运行正常,并阻止主线程直到整个组完成。

所以说chunks包含4项工作量,下面的代码会旋转4个并发工作者,然后等待所有工作者完成:

let group = DispatchGroup()
let queue = DispatchQueue(label: "", attributes: .concurrent)

for chunk in chunk {
    queue.async(group: group, execute: DispatchWorkItem() {
        do_work(chunk)
    })
}

_ = group.wait(timeout: .distantFuture)

答案 1 :(得分:6)

就像使用Objective-C CLI一样,您可以使用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
    }
}
  

正如Martin指出的那样,您可以使用NSDate.distantFuture() as NSDate代替NSDate(timeIntervalSinceNow: 2)。 (强制转换是必要的,因为distantFuture()方法签名表示它返回AnyObject。)

如果您需要访问CLI参数see this answer。您还可以return exit codes using exit()

答案 2 :(得分:4)

Swift 3最小化Aaron Brager解决方案的实现,它简单地将autoreleasepoolRunLoop.current.run(...)结合起来,直到你打破循环:

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

答案 3 :(得分:2)

我认为CFRunLoopNSRunLoop更容易

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