Creating exactly N threads with Grand Central Dispatch GCD
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Grand Central Dispatch is designed so you usually do not create or own an exact number of threads directly. If your real requirement is "run at most N tasks concurrently," GCD can express that, but if your requirement is literally "spawn exactly N OS threads and keep them fixed," that is not the abstraction GCD is built around.
What GCD Actually Controls
GCD manages queues of work, not a user-owned thread pool. You submit blocks, and the system decides how many worker threads to schedule based on load, quality of service, blocking behavior, and other runtime conditions.
That is why this question often needs reframing.
Usually, the real goal is one of these:
- limit concurrency to N tasks at a time
- serialize access to a resource
- parallelize work without oversubscribing the CPU
GCD handles those goals well. Exact thread ownership is what lower-level threading APIs are for.
Limit Concurrency with a Semaphore
A common GCD pattern is to submit many tasks to a concurrent queue while using a semaphore to ensure only N are active at once.
This does not guarantee exactly three threads exist. It guarantees that at most three tasks are in the critical execution region at the same time.
For most application-level needs, that is the right control point.
Use an OperationQueue for a Clearer Concurrency Limit
If the requirement is specifically "no more than N concurrent operations," OperationQueue is often simpler than hand-rolling a GCD pattern.
Again, this constrains concurrent work, not an exact persistent thread count. But it matches the intent much more closely than manual thread management.
Why Exact Thread Count Is a Bad Goal Most of the Time
The system scheduler already knows more about CPU availability and thread pressure than your application does. Forcing an exact thread count can:
- reduce flexibility under changing load
- create too many blocked threads
- make performance worse on small or large devices
- work against GCD's optimization strategy
If your task is CPU-bound, the right concurrency level may be close to the number of available cores. If your task blocks on I/O, the best level may be different. GCD is designed to adapt to that reality.
When You Truly Need Dedicated Threads
There are special cases where you really do need a long-lived dedicated thread, such as legacy APIs tied to a run loop or thread-local assumptions. In those cases, GCD may not be the right primary abstraction.
That is when you might use:
- '
Thread' - '
pthread' - a custom worker model outside GCD
But this should be the exception, not the default, especially on Apple platforms where GCD is intended to be the standard concurrency primitive.
QoS and Queue Design Still Matter
Even when you are not controlling exact threads, you should still choose the right queue characteristics.
Quality of service affects scheduling priority. That often matters more to user-perceived performance than whether exactly N threads existed at a given moment.
Common Pitfalls
A common mistake is equating "N concurrent tasks" with "N threads." GCD may run those tasks using fewer or more worker threads over time depending on how they behave.
Another issue is blocking GCD threads heavily and then blaming GCD for spawning additional threads. If tasks block often, the runtime may compensate.
Developers also sometimes use semaphores correctly but then believe they have built a fixed-size thread pool. They have limited concurrency, which is useful, but not the same thing.
Finally, if OperationQueue.maxConcurrentOperationCount matches your need, prefer that clearer API over convoluted queue plus semaphore patterns.
Summary
- GCD is about scheduling work, not owning an exact fixed set of threads.
- Most real requirements are about limiting concurrency, not thread count.
- Use a semaphore or
OperationQueue.maxConcurrentOperationCountto cap active work. - Exact thread creation is usually the wrong goal on Apple platforms.
- Reach for lower-level thread APIs only when you truly need dedicated threads.

