Parallel programming

As you may remember, we've already talked about asynchronous programming, when we were dealing with the async/await keywords that appeared in .NET Framework 4.5 as a solution to avoid performance bottlenecks and improve the overall responsiveness of our applications.

Parallelism was present earlier, in version 4.0 of the framework, and it was programmatically related to the Task Parallel Library (TPL). But first, let's define the concept of parallelism (at least according to Wikipedia):

"Parallelism is a form of computation in which several operations can execute simultaneously. It's based on the 'Divide and Conquer' principle, fragmenting a task in smaller tasks, which are later solved in parallel."

This is, obviously related to hardware, and we should be aware of the difference between multiple processors and multiple cores. As Rodney Ringler says in his excellent book C# Multithreading and Parallel Programming by Packt Publishing:

"A multiple core CPU has more than one physical processing unit. In essence, it acts like more than one CPU. The only difference is that all cores of a single CPU share the same memory cache instead of having their own memory cache. From the multithreaded parallel developer standpoint, there is very little difference between multiple CPUs and multiple cores in a CPU. The total number of cores across all of the CPUs of a system is the number of physical processing units that can be scheduled and run in parallel, that is, the number of different software threads that can truly execute in parallel."

Several types of parallelism can be distinguished: at bit level, at instruction level, data parallelism, and task parallelism. And this is at the software level.

There's another type of parallelism, at the hardware level, in which distinct architectures can be implied, offering distinct solutions depending on the problem to be solved (there's a particularly exhaustive explanation published by Lawrence Livermore National Laboratory if you're interested in this topic, Introduction to Parallel Computing at https://computing.llnl.gov/tutorials/parallel_comp/. We'll stick to the software level, of course.

Parallelism can be applied in many different areas of computing, such as the Monte-Carlo Algorithm, Combinational Logic, Graph Traversal and Modeling, Dynamic Programming, Branch and Bound methods, Finite-state Machines, and so on.

From a more practical perspective, this translates into solving problems related to a wide variety of areas in science and engineering: astronomy, weather, rush hour traffic, plate tectonics, civil engineering, finance, geophysics, information services, electronics, biology, consulting, and, in a more everyday approach, any process that takes certain time and that can be improved thanks to these techniques (downloading data, I/O operations, expensive queries, and so on).

The process followed in computing in parallel is explained in the previously mentioned source in four steps:

  1. A problem is broken into discrete parts that can be solved concurrently.
  2. Each part is further broken down into a series of instructions.
  3. Instructions from each part execute on different processors simultaneously.
  4. An overall control/coordination mechanism is employed.

Note that the computational problem has to be of a nature such that:

  • It can be broken into discrete fragments of work that can later be solved simultaneously
  • It has to be possible to execute several instructions at any moment in time
  • It should be solved in less time using multiple resources or the computer than would be with a single resource

The resulting architecture can be explained in a graphic schema, as follows:

Parallel programming

Difference between multithreading and parallel programming

It's also important to remember the difference between multithreading and parallel programming. When we create a new thread in a given process (review the discussion about this in the first chapter if you need more references), that thread is scheduled by the operating system, which associates it with some CPU time. The thread executes the code in an asynchronous manner: that is, it goes its way until it finishes, a moment in which it should be synchronized with the main thread in order to obtain the results (we've also talked about updating main threads earlier).

However, at the same time, there are other applications in execution in our computer (think of services, among other things). And these applications are also given their corresponding CPU time; so, if our application uses more than one thread, it's also given more CPU time, and the results are obtained more rapidly without blocking the UI thread.

Moreover, if all this is executed in one core, we're not talking about parallel programming. We can't talk about parallel programming if we don't have more than one core.

Tip

A typical mistake that we see is when a program executes in a virtual machine, and the code uses parallel approaches because in a virtual machine we only use one core by default. You have to configure the VM to work with more than one core in order to take advantage of parallelism.

Also, from the everyday programmer point of view, you can mainly divide the types of tasks subject to parallel programming into two principal areas: those that are CPU-bound and those that are I/O bound (we can also add another two, Memory Bound, the amount of memory available is limited with respect to a process, and Cache Bound, which happens when the process is limited by the amount and the speed of the available cache. Think of a task that processes more data than the cache space it has available).

In the first case, we're dealing with code that would run faster if the CPU were faster, which is the case where the CPU spends the majority of time using the CPU cores (complex calculations being a typical example).

The second scenario, (I/O-bound) happens when something would run faster if the I/O subsystem could also run faster. This case might happen in different scenarios: downloading something, accessing disk drives or network resources, and so on.

The first two cases are the most common, and this is where TPL comes into play. Task Parallel Library appeared as a solution to implement parallel coding in our applications linked to the first two scenarios: CPU-bound and I/O-bound.

Programmatically, we can find it in three flavors: Parallel LINQ, the Parallel class, and the Task class. The first two are mainly used for CPU-bound processes, while the Task class is more suitable (always generally speaking) for I/O-bound scenarios.

We already saw the basics of working with the Task class, which also allows you to execute code asynchronously, and here, we'll see how it can also perform cancelations (with tokens), continuations, synchronization of contexts, and so on.

So, let's review these three flavors to look at some typical solutions to coding problems in which these libraries have noticeable improvements.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.37.126