Search code examples
swiftgrand-central-dispatchswift-nio

Does SwiftNIO have a mechanism similar to GCD barriers?


Can GCD and SwiftNIO co-exist?

Pardon me ignorance. This has me confused.

Reasoning:

  • Having too many threads is detrimental to performance;
  • GCD creates and manages its own threads;
  • SwiftNIO creates and manages its own threads;
  • If you use many different framework/library each creating and managing their respective threads, you can end up with too many threads;

What I’m trying to accomplish:

I need to have multiple independent tasks occur concurrently (handled by SwiftNIO) but occasionally, once all previous tasks are completed, run a single serial task (and maybe default to a different behaviour with SwiftNIO during that time). GCD has dispatch barriers for this purpose but SwiftNIO, to my knowledge, has no similar mechanism.


Solution

  • Yes, SwiftNIO and GCD can absolutely co-exist.

    SwiftNIO is a non-blocking and asynchronous networking framework such that you can run an arbitrary number of tasks/network connections/... on a very small number of threads.

    In fact, you can run any correct SwiftNIO program on just 1 thread (which can even be the main thread if you so choose) and it will work just fine. The only reason to even allow SwiftNIO to use more than one thread is to make use of the CPU resources you pay for anyway. So for example, let's assume you're implementing a network server that is supposed to handle 100,000 connections concurrently and you machine has 4 CPUs. You could totally handle all those 100,000 connections on just one thread and the program would work fine but you would only use one of your four available cores. That makes the program unnecessarily slow and you waste four CPU cores. In this example, I would then recommend to spawn an EventLoopGroup with 4 threads, then 100,000 connections will then be round-robin'd across the four loops meaning that each loop (and therefore thread) should get about 25,000 connections and you have a chance to use all your available hardware resources.

    Regarding your other question on when to trigger one operation after a number of other operations have succeeded: When using SwiftNIO, your operations probably look something like func myOperation() -> EventLoopFuture<Void>, now let's assume you wanted to run myOperation 100 times concurrently and then print "Hello World!" once they have all succeeded. How you'd do that in NIO would be:

    // Spawn `myOperation` 100 times, the array will contain 100 futures which
    // will contain the result (or failure) of the 100 runs of `myOperation`.
    let hundredOps = (0..<100).map { _ in
        myOperation()
    }
    
    // Now we do the "barrier":
    EventLoopFuture<Void>
        // using `andAllSucceed` we create one future
        // that will be fulfilled when all of the 100 futures from above are succeeded.
        .andAllSucceed(hundredOps, on: eventLoop)
        // And once that "overall future" is succeeded, we print "Hello World!"
        .whenSuccess {
            print("Hello World!")
        }