In Ruby, what is often called “thread contention” is, a queuing mechanism managed by the Ruby Virtual Machine through the Global VM Lock (GVL). When you have multiple Ruby threads, each thread is placed in an orderly queue by the VM. Only one thread at a time can hold the GVL and execute Ruby code. The thread holds the GVL until it gives it up voluntarily or the VM takes it back, then rejoins the queue to wait its turn again. This mechanism defines “thread contention” in practice—it is simply a GVL queue.
When you create a new thread in Ruby (Thread.new), it is added to the back of this queue. The Ruby runtime (not the operating system) decides which thread holds the GVL next, even though the threads themselves are implemented as native OS threads. The OS handles preemptive multitasking at a lower level, but the Ruby VM maintains strict control over the GVL, dictating which thread can run Ruby code at any moment. Concurrency in Ruby is fundamentally different from languages without an equivalent of the GVL—the real bottleneck for multithreaded Ruby is just waiting in line for the GVL.
Key
In practical terms, if you increase the thread pool size in servers like Puma or Sidekiq beyond what your workload can parallelize, you can see increased latency due to more threads competing for access to the GVL (i.e., more time spent “in the queue”). So, “thread contention” is not about threads fighting over arbitrary resources or data, but about orderly waiting in the GVL access queue.
This queuing impacts what Ruby workloads benefit from threads—IO-bound workloads benefit more, while CPU-bound workloads will see bottlenecks due to the GVL, causing confusion over what “contention” really means in this context. For parallelism with Ruby, understanding the GVL and its queue is crucial for architecting scalable, performant applications in the Ruby ecosystem.