As multi-core processors become more and more widespread, the ability to write programs that use all available processors becomes more and more important. Let's look at why existing widely used tools for writing programs for multi-core processors are not a good enough solution, what is transactional memory, and how it solves this problem.
As a rule, programs (if they are not written in a purely functional language) have variable memory areas in which these programs are located. If a program has several control threads that work with this data, it is important that access to it be structured in such a way that there are no such problems with parallel access, such as reading a memory area that is written in parallel from another thread, or writing from two threads at the same time.
The most common means of synchronizing data access in programs in imperative languages ββis blocking. Before accessing the data, a lock should be taken on them; if the lock is already taken, the thread waits for the moment when the lock is released. Thus, we can be sure that more than one stream is never accessible to the same data. Unfortunately, there is a problem. It would be desirable, that many flows could work simultaneously. Therefore, it would be logical to structure the program so that for each less independent part of the program data its own lock is made. But, when we have many locks, there is a possibility of deadlock: in the case when locks are taken in a different order. To fight them, you need to take the lock in the same order. For this, for each method, you need to understand which locks can be taken by the caller, and which locks can be taken during this call, and make sure that the order of locks is always the same. Making it work very hard, and even if the program is running, inaccurately adding a call in the wrong place can lead to deadlock.
')
What could be an alternative to locks? One of the solutions to accessing shared data is transactional memory. Transactional memory allows you to work with data using transactions similar to database transactions. Transactions are executed as if the current transaction is the only operation on the current data. Upon completion of the transaction, it looks like there were no conflicts with other transactions. If they were not (the most likely option), the change is accepted, if not, the transact is repeated again. In programs where there are a large number of threads working with different areas of memory, this scheme will work very well: conflicts will be rare, and the degree of parallelism will be high. Unfortunately, there are some drawbacks: in transactional blocks, it is desirable not to cause code that has side effects: input / output, drawing on the screen, and so on.
Currently, there are quite a large number of libraries (for example,
MultiVerse for Java) that allow using this approach in existing programming languages, and also some languages ββ(Fortress, Clojure) that support transactional memory directly.