Talks

Let's talk concurrency

Let's talk concurrency

by José Valim

In the talk "Let's talk concurrency," José Valim discusses the evolving landscape of concurrency, particularly in multi-core systems. He highlights the complexities of traditional thread-based concurrency and introduces alternative paradigms such as Software Transactional Memory (STM) and message passing. The discussion emphasizes how these new approaches can alleviate the common pitfalls associated with shared mutable state in concurrent programming.

Key Points Discussed:
- Definition of Concurrency: Valim starts with a simple definition, noting that concurrency involves executing multiple tasks simultaneously, using the example of processing a large CSV file in parts to expedite calculations.
- Single-Process Multithreaded Concurrency: He focuses on utilizing multiple cores within a single process rather than launching multiple instances, which is often inefficient.
- Theoretical Perspectives: Valim presents a theoretical model without state and discusses functional processes that are deterministic and free of side effects, which allows for significant optimizations by the runtime.
- State and Concurrency Complexity: Introducing state complicates concurrency, particularly when shared mutable state leads to race conditions and inconsistent results from concurrent modifications.
- Locks and Their Downsides: Traditional solutions involving locks help manage access to shared resources but create additional burdens for developers in terms of potential deadlocks or race conditions.
- Software Transactional Memory (STM): Valim explains STM as an optimistic concurrency model which allows threads to operate simultaneously while checking for conflicts before committing changes.
- Message Passing Model: He discusses this model, which eliminates shared state by having components communicate through messages. This promotes modularity and scalability, especially in distributed systems.
- Final Recommendations: Valim encourages further exploration of concurrency concepts, suggesting resources like "Seven Languages in Seven Weeks" and frameworks such as Celluloid in Ruby that facilitate learning about message passing paradigms.

Conclusions: Valim sums up by recognizing the various concurrency models and encourages developers to assess which paradigm will best suit their application's needs. The importance of understanding concurrency in the context of modern multi-core systems is emphasized.

This presentation pushes developers to rethink their approach to concurrency, noting how various paradigms can lead to better efficiency and easier maintenance in software design.

00:00:09.599 Hello everyone! My name is José Valim, and I am the co-founder of Platform Tech, a consultancy based in Brazil. We work with Ruby on Rails and collaborate with companies worldwide. You may know me and Platform Tech through our open-source projects, such as Devise and Simple Form.
00:00:16.960 I am also a member of the Rails Core team, and I authored the book 'Crafting Rails Applications,' which discusses Rails internals and how to better understand and utilize Rails. However, today, I'm not here to talk about Rails or even specifically about Ruby. Instead, I will focus on concurrency in general.
00:00:30.599 I want to give you an overview of why concurrency is changing and becoming an increasingly popular topic. Different programming languages are exploring various approaches to concurrency, and I hope to provide you with sufficient information so you can delve deeper into the subject later.
00:01:08.320 Let’s start with a very simple definition of concurrency: executing several tasks simultaneously. For example, imagine you have a large CSV file and want to get the total sum of a specific column. If the file is substantial, it can take a significant amount of time to process. One straightforward approach is to divide this task into two halves, processing each half concurrently to speed up the calculation.
00:01:22.680 This is a basic example of concurrency: breaking a file into parts and calculating the sums simultaneously. Another example familiar to Rails developers involves using Passenger. When deploying a Rails application, you can configure Passenger to start multiple instances. This method represents one form of concurrency—multiprocessing.
00:01:55.040 However, I won't focus on that kind of concurrency today. As we look toward the future, many of us will want to deploy software on machines with 16 or even 24 cores. You wouldn’t want to start 16 Rails instances to utilize all those cores effectively. Thus, my focus today is on single-process multithreaded concurrency, where multiple cores within a single process are effectively utilized.
00:03:23.959 Before discussing concurrency, let’s consider a theoretical world without state or concurrency—a declarative model. This model is heavily based on mathematics, allowing us to perform computations without changing state. Here, we cannot mutate anything. In programming, for instance, in Ruby, you might have an array and append an element to it, resulting in mutation. However, in a declarative model, you would create a new array instead of changing the original.
00:04:05.319 In our theoretical world, we define functional processes—where functions are deterministic. This means that for the same input, the function will yield the same output each time. If we read from a file or generate a random number, those processes cannot be deterministic since the output can change each time we call them. Therefore, in this ideal world, everything in our code lacks side effects.
00:05:07.560 No function can change the output of other functions because the model relies on the same input consistently yielding the same output. This lack of side effects is crucial because it allows the language runtime to perform numerous optimizations. For instance, if we have a function that computes a value based on known input, and we frequently call this function, the language can optimize it by reusing the result each time instead of recalculating it.
00:05:58.000 Following this, let’s introduce concurrency into our state-free theoretical world. Suppose we now have state, but we still have a form of concurrency called data flow variables. Using the same Lambda function, we can define that we want to calculate values concurrently. Instead of calculating them sequentially, we can use threads to compute the results simultaneously.
00:06:49.839 In this model, when the first thread calculates a value, it starts another thread, allowing both calculations to occur in parallel. The runtime simply waits for the completion of these threads and retrieves the values as needed. This method of leveraging threads works smoothly because there is no shared mutable state—each thread works independently, avoiding the race conditions that often plague concurrent programming.
00:08:04.240 However, the need for state introduces complexity. Let’s consider a simple example involving a shared counter that multiple threads are attempting to increment. This leads to shared state issues, where race conditions can occur if multiple threads read and modify the counter simultaneously without proper control.
00:09:11.320 When two threads operate on the same counter, they may end up with inconsistent results due to their concurrent actions. Ensuring that only one thread can modify the counter at a time is essential to prevent these inconsistencies. Most programming languages solve these issues with locks or similar mechanisms.
00:10:02.599 The basic solution typically involves implementing a lock to ensure mutual exclusion. When a thread reaches the code section involving counter modification, it acquires the lock, preventing other threads from entering that section until the first thread has completed its operation.
00:10:38.760 While locks control access effectively, they also place a burden on developers, who must remember to use them correctly. Mistakes can lead to deadlocks or race conditions if locks are forgotten or misapplied. It creates a pessimistic form of concurrency management, limiting the number of threads performing operations.
00:11:15.119 An alternative approach is to use Software Transactional Memory (STM), designed to simplify concurrency by allowing multiple threads to work with shared memory more efficiently. It offers a more optimistic approach, where threads can execute concurrently, and the system only checks for conflicts when they try to commit changes.
00:12:31.920 If no other thread has modified the shared state during the transaction, it commits its changes. If a change has occurred, it rolls back and retries, maintaining consistency without requiring explicit locks in most scenarios. This model is more flexible and robust, as it avoids common pitfalls associated with locking mechanisms.
00:13:30.440 Finally, let's explore the message passing approach to concurrency, which eliminates shared state altogether. Messages are sent between isolated components where state is encapsulated. This approach encourages the design of systems where components collaborate through message passing, increasing modularity and ease of maintenance.
00:14:13.719 In this model, a server function handles incoming messages. It reacts based on the message type—incrementing a counter or returning its current value. By having dedicated instances managing their own state, we can achieve concurrency without worrying about modification conflicts, as threads interact only through message exchange.
00:15:02.760 The message passing model results in higher scalability and simpler coordination, especially for distributed systems, since it can easily expand with additional clients communicating with the server. Implementing location transparency allows seamless integration across different nodes in a network.
00:15:52.960 In conclusion, I discussed several concurrency models: traditional shared memory, locks, Software Transactional Memory, and message passing. Each paradigm has advantages and disadvantages, and selecting the appropriate solution often depends on the specific challenges we face in our applications.
00:17:41.360 If you want to explore these topics further, I recommend resources like 'Seven Languages in Seven Weeks,' which covers several programming languages, including Clojure, Erlang, and Haskell. These languages offer insightful approaches to concurrency and immutability that can enhance your understanding.
00:18:51.480 Lastly, there are frameworks like Celluloid that support message passing in Ruby, allowing you to experiment with concurrent programming paradigms without switching languages. Thank you very much for your attention!
00:19:41.440 Now, I'd like to open the floor for questions. Feel free to ask anything related to concurrency or the topics I covered today.
00:20:36.160 Thank you for your questions! To summarize, message passing indeed runs concurrently in isolated environments, allowing seamless communication between components while managing their state effectively.
00:20:57.680 As we delve into concurrency, it’s important to evaluate each model for its specific circumstances, ensuring effective and efficient program design and performance.