There has been various discussion about how rules/scripts handles concurrency and parallelism in OH lately. Quite a lot of it ended up in a documentation PR, while the topic has also been covered in this marketplace topic, and I’m sure other places as well.
The idea is to continue this here, as a place where it’s easier to find, both now and in the future. As I see it, there are two main motivations behind the discussion: To help clarify how things actually works and take the guesswork out of it, and perhaps find to find areas that can be improved or handled differently.
There are different terms used, threads, concurrency, parallelism, thread-safety, locks, synchronization. It can be a handful just to keep track of what people mean by the different terms, I’m not sure that everybody’s definitions are the same.
I’ll try to define some terms, both what I can find of online definitions and perhaps my interpretation if these differ.
- Thread: Wikipedia: “In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.[1] In many cases, a thread is a component of a process.”
I’ll add that threads are OS entities, and that although there can be minor differences in their properties between OSes, they generally behave very similarly and can be thought of as the same concept. Threads generally belongs to a process, and they all share access to the memory allocated to the process. - Process: Wikipedia: “In computing, a process is the instance of a computer program that is being executed by one or many threads. There are many different process models, some of which are light weight, but almost all processes (even entire virtual machines) are rooted in an operating system (OS) process which comprises the program code, assigned system resources, physical and logical access permissions, and data structures to initiate, control and coordinate execution activity. Depending on the OS, a process may be made up of multiple threads of execution that execute instructions concurrently.”
I’ll add that like threads, processes are OS entities, and they generally don’t share memory with other processes (although it’s possible to do so under certain circumstances). Processes can’t communicate with each other unless they implement some way to do that (through network, pipes, sockets etc.). - Fork/forking: Wikipedia: “In computing, particularly in the context of the Unix operating system and its workalikes, fork is an operation whereby a process creates a copy of itself. It is an interface which is required for compliance with the POSIX and Single UNIX Specification standards. It is usually implemented as a C standard library wrapper to the fork, clone, or other system calls of the kernel. Fork is the primary method of process creation on Unix-like operating systems.”
I’ll add that forking usually means to create a new process, unfortunately, I’ve also seen it used to describe operations with threads. - Concurrency: Wikipedia: “In computer science, concurrency refers to the ability of a system to execute multiple tasks through simultaneous execution or time-sharing (context switching), sharing resources and managing interactions. Concurrency improves responsiveness, throughput, and scalability in modern computing”
I’ll add that that I’ve seen some use concurrency about things that are scheduled to run one after each other, like in the event-loop in JavaScript. This doesn’t correspond with my understanding of the concept. In a single CPU system, the OS will schedule different threads to run at different times, so none of the threads actually do anything simultaneously. The scheduling is handled by the OS kernel, and execution of one thread doesn’t prevent the execution of another, the scheduler will switch to the next one regardless of whether one thread is finished or not. This is very different from how e.g the even-loop works. Since it runs in a single thread, any holdup/loop/bug can grind everything to a halt.
Also essential, to me, is that you never know (nor care) if threads actually run simultaneously or not. You know that this depends on the hardware that runs the code, and if there are multiple cores available, multiple threads can (and probably will) run simultaneously. As a consequence, you take precautions as if they run simultaneously regardless. This is also very different from the event-loop situation, where you know that things will never happen simultaneously, so you don’t have to take precautions to prevent problems that can occur if multiple threads tires to access the same memory at the same time. - Parallelism: Wikipedia: “In computer science, parallelism and concurrency are two different things: a parallel program uses multiple CPU cores, each core performing a task independently. On the other hand, concurrency enables a program to deal with multiple tasks even on a single CPU core; the core switches between tasks (i.e. threads) without necessarily completing each one. A program can have both, neither or a combination of parallelism and concurrency characteristics”
I don’t have much history in using the term, so my definition is somewhat vague. I see that some use it to define when things happen simultaneously, while others seem to mean that it implicates several running processes that “work towards a common goal”. I guess I’d say that if two threads don’t work with any shared objects, but runs something external like a script engine that is only known by each individual thread, I could say that the “threads work in parallel”. That doesn’t fall under concurrency to me, because the core of the concurrency concept is the utilization of shared objects/memory. - Thread-safety: Wikipedia: “In multi-threaded computer programming, a function is thread-safe when it can be invoked or accessed concurrently by multiple threads without causing unexpected behavior, race conditions, or data corruption.[1][2] As in the multi-threaded context where a program executes several threads simultaneously in a shared address space and each of those threads has access to every other thread’s memory, thread-safe functions need to ensure that all those threads behave properly and fulfill their design specifications without unintended interaction.”
I don’t have much to add, I’d say that thread-safe code means code that, through any available means, is made in such a way that it can be used by multiple threads at once without potentially behaving “unexpectedly”. Care has been taken to make sure that things can’t go wrong, not merely that they just most likely won’t go wrong. - Lock/Mutex and synchronization: Wikipedia: “In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive that prevents state from being modified or accessed by multiple threads of execution at once. Locks enforce mutual exclusion concurrency control policies, and with a variety of possible methods there exist multiple unique implementations for different applications.”
“Lock” and “Mutex” are the same thing in principle, but one or the other word might be more commonly used in certain contexts. “Synchronization” is a wider term, but is often used to describe a lock/mutex, particularly among Java developers because Java has a keyword “synchronized” that activates an intrinsic lock. The keyword “synchronized” is the by far most common way to invoke a lock in Java, which is why they tend to be used more or less like synonyms.
Generally though, “synchronization” only means that some mechanism is used to make sure that things happen in a certain order, usually serially (not concurrently). Locks/mutexes is one way to achieve this, because a lock/mutex can be acquired by a thread, and any other thread that attempts to acquire the same lock/mutex must wait until the previous thread has released the lock/mutex before they can continue. The effect is thus that access to the code inside a lock/mutex, is “synchronized” is that threads have to run that code in turn. It is used to ensure that only one thread can access a certain part of memory at the same time, if that part of memory can be changed (is mutable). Parts of memory that can’t change (are immutable) can be safely accessed by multiple threads concurrently without any form of synchronization.
There is a lot more to be said about this topic, but I deem that this post is long enough as it is now.
