Blue Ridge Ruby 2024

Lightning Talk: Things that you can (but not necessarily should) do with threads in Ruby/Rails

Lightning Talk: Things that you can (but not necessarily should) do with threads in Ruby/Rails

by Michael King

In this lightning talk, Michael King discusses various aspects of using threads in Ruby and Rails, highlighting both their advantages and potential pitfalls. The talk focuses on the following key points:

  • Concurrency in Ruby/Rails: Ruby's Thread class allows developers to implement concurrency, which can enhance application performance by dividing work across multiple threads. However, it brings challenges such as race conditions, deadlocks, and synchronization issues.

  • Producer-Consumer Problem: King introduces the producer-consumer problem, where a producer thread creates items while a consumer thread processes them from a shared queue. He emphasizes the importance of ensuring that the queue does not overflow or underflow. King mentions Ruby's Queue class and how many developers often resort to using Redis for similar functionality, mentioning Redis's thread-safe characteristics.

  • Deadlocks: The speaker explains deadlocks through the lens of the Dining Philosophers problem, illustrating the challenges threads face when waiting indefinitely for resources. King suggests using mutexes to avoid deadlocks, likening them to bouncers that manage access to resources.

  • Overhead of Threads: King warns that more threads do not inherently lead to faster execution. He shares a simulation demonstrating that while having ten threads is beneficial, increasing to 100 or 1,000 threads may introduce so much overhead that it negates the expected performance gains. This reinforces the idea that concurrency should be employed judiciously.

  • Conclusion: The speaker concludes with a reminder of the importance of questioning whether a problem truly requires concurrency, and to use threading wisely to navigate potential issues while taking advantage of Ruby's concurrency solutions. Throughout the session, King emphasizes the key takeaways of being aware of threading pitfalls while leveraging the advantages it offers with proper caution.

Michael King, a senior developer at TripleSeat, provides additional resources, including a QR code linking to the repository for attendees interested in exploring further.

00:00:11.960 All right, so these are some things that you can, but not necessarily should, do with threads in Ruby and Rails. Ruby provides us with a Thread class that we can use to implement concurrency. What this means is that we can divide and conquer, breaking up work among multiple threads to achieve tasks a little bit faster.
00:00:22.160 However, there are some problems associated with concurrency. We can encounter issues such as race conditions, deadlocks, and synchronization problems. It's important to be aware of the overhead involved in threading. We call a method or operation 'thread-safe' if it is resilient to these issues.
00:00:41.120 Let’s discuss our first challenge: the producer-consumer problem. This is where we have a producer thread, a consumer thread, and a shared queue. The producer adds items to the queue while the consumer takes items out. We need to ensure that the producer doesn’t add items to a full queue and that the consumer doesn’t remove items from an empty queue.
00:00:58.079 Fortunately, Ruby provides us with a Queue class, which implements a multi-producer, multi-consumer model. However, to be realistic, many of us are probably using Redis for such tasks anyway. You can use a Redis list with operations like RPUSH and LPOP to achieve similar functionality. In my experience, Redis is relatively thread-safe.
00:01:28.479 I have used threads in a project I call the Economy Simulator 9000. In this app, some producer threads create widgets, while consumer threads consume them. You can observe how supply and demand ebb and flow based on the numbers of producers and consumers involved.
00:01:53.479 Additionally, I’m using Action Cable to transmit data from individual threads to the front end. This is how I visualize the threading in my app.
00:02:01.119 Now, let's move on to our second challenge: deadlocks. A deadlock occurs when threads are waiting indefinitely for resources to become available. You might be familiar with the Dining Philosophers problem, where five philosophers share chopsticks. Each needs two chopsticks to eat, leading to a potential deadlock if they each hold one chopstick.
00:02:11.959 To resolve this, you can use a mutex. Here, the philosophers represent the threads, and the chopsticks symbolize resources. A mutex acts like a bouncer at a club. When the resource is busy, the mutex prevents other threads from accessing it until it becomes available.
00:02:26.080 In my demo app, you can see how the philosophers wait for their chopsticks to become available before they take a bite to eat. Once they’re done, they put the chopsticks down, but they must wait for their neighbor's chopsticks to be available for their next bite.
00:02:54.040 Now, let's discuss our third challenge: too much overhead. More threads do not always equal faster execution. Spawning too many threads can actually slow things down due to increased overhead. Keep in mind that not all problems warrant concurrency.
00:03:14.640 For example, I have another simulation where I’m generating a report with 100,000 rows and five columns of random data. When running it with one thread, two threads, five threads, and ten threads, you can see that the ten-thread execution finishes first due to its ability to process more work in less time.
00:03:40.880 However, if you increase the number of threads to 100 or 1,000, you might notice that the overhead starts to negate the benefits of having more threads. To recap, threads are powerful, but use them wisely. Ruby provides many solutions for concurrency and thread safety. You can transmit data while a thread is running using Action Cable, but always be mindful of overhead and avoid adding more threads than necessary. Always question whether the problem at hand truly requires concurrency.
00:04:16.959 I’m Michael King, a senior developer at TripleSeat. Here’s some of my information, along with a QR code to the repository if you’d like to check it out.
00:04:29.840 Oh.