How to engage all the cores of a CPU?

Multithreading and multiprocessing are two forms of computational parallelism that could be used to occupy your cores.

Multithreading involves mutiple parallel execution units called “threads” which share a common memory space and resources within a single process. In Julia, this is mainly handled through the Base.Threads module though packages with alternatives exist.

Multiprocessing involves parallel execution units called “processes” which each have their own memory space and resources. In Julia, this mainly handled through the Distributed standard library package though other packages with alternatives exist.

Multithreading can be more efficient than the multiprocessing model by taking advantage of shared resources. However, multiprocessing can be simpler due to a clear separation of resources. That said these distinctions are not so simple since there are ways to separate resources between threads and share resources between processes.

Julia uses a multithreading model based on virtual threads called Tasks. These virtual threads can be dynamically scheduled on actual threads to achieve parallelism. The potential advantage of virtual threads is that they can be created inexpensively compard to real threads.

To achieve parallelism to occupy all of your cores, you the programmer must ideally find opportunities when computational tasks can run independently of each other. In Julia, a basic parallel form looks like the following.

Threads.@threads for i in 1:16
    independent_task(i)
end

In practice, purely independent tasks are rare and some coordination mechanisms are required.

To simplify parallelism, some packages provide parallel versions of common tasks such as summation. For example, Folds.sum provides mechanisms to parallelize summation using either threads or processes.

4 Likes