CodeSport (March 2012)

Concurrency and threads

In this month’s column, we take a quick look at the threading support introduced in the new standard for C++11.

One of the major events of 2011 in the programming world was the official release of the new C++11 standard. This was not a minor revamp with just a few new features, but a full-blown release of the new standard for C++, with a number of radically new features, such as lambda expressions, automatic type deduction of objects, rvalue references, etc. As Bjarne Stroustrup put it aptly, “C++11 feels like a new language — the pieces just fit together better.”

Other than the language features, there are a whole new set of features introduced in the C++ standard library as well. A number of new container classes, new algorithms, smart pointers, atomic operations and a new type of traits have been introduced. However, the most important addition to the C++ standard library is the threading support.

In this column, we will discuss how threads are supported in C++11, with a couple of examples. First, let’s start off with concurrency, and how threads can be used to implement it.

Concurrency and threads

Concurrency refers to the ability to perform multiple independent sets of operations in parallel, instead of executing the operations in a single stream, sequentially. Concurrency can be achieved by means of either processes or threads. When concurrency is supported by means of a multi-process mechanism, each process gets its own copy of the data, and it needs to communicate using standard inter-process communication mechanisms such as pipes, message-passing or explicitly created shared memory segments.

The other way of achieving concurrency is through multiple threads in a single process. Using multiple threads of the same process to achieve concurrency is a lightweight approach, since the data is shared across threads of the same process, using shared memory. However, this requires synchronisation and cooperation among the threads, and hence imposes a certain programming burden on the developer.

Mechanisms for creating threads

In the UNIX/Linux world, the standard mechanism of creating threads is through the POSIX threads (popularly known as pthreads) interfaces (Windows supports WinThreads, which offers similar functionality). Here is a simple program that prints “hello world” with just two threads:

#include <pthread>
void print_hello(void)
{
          printf("hello world\n");
}
int main (void)
{
     printf("Main Thread - hello world\n");
     pthread_t thread_secondary;
     //create the auxiliary thread
     pthread_create(&thread_secondary, NULL, &print_hello, NULL);
    //join the auxiliary thread with main thread
    pthread_join(&thread_secondary, NULL);
    return 0;
}

This requires us to understand and use low-level thread-creation APIs such as pthread_create to create the thread, and p_thread_join to join the auxiliary thread with the main thread. However, with the support for threading in C++11, developers need not concern themselves with these low-level details. Here is a code snippet that demonstrates the “Hello World” code in C++11:

#include <iostream>
#include <thread>
//Function called from auxiliary thread
void print_hello(void) {
        std::cout << "Hello, World!" << std::endl;
}
int main()
{
     std::cout << "Hello, World from Main Thread" << std::endl;
    //Create the auxiliary thread
  std::thread thread1(print_hello);
 //join the auxiliary thread with main thread
 thread1.join();
 return 0;
}

In the above example, we used std::thread to create a new thread, which runs the task print_hello. All the low-level details of using pthread interfaces are now hidden for the developer. A rough skeleton of the thread class in C++11 is shown below:

class thread {
    public:
      class id;
      template <class F, class … Args> thread (F&& f, Args&&… args);
     bool joinable() const;
     void join();
    void detach();
    id getid const();
    …….
}

In the above example, we did not pass any arguments to the function print_hello, with which we invoked the auxiliary thread. Let us assume that we decided to pass in an integer variable to print_hello. Here is how the modified code looks:

#include <iostream>
#include <thread>
//Function called from auxiliary thread
void print_hello(int j) {
        std::cout << "Hello, World!" << j << std::endl;
}
int main()
{
    int i = 2;  //signifiying that it is the second thread
     std::cout << "Hello, World from Main Thread" << std::endl;
    //Create the auxiliary thread
  std::thread thread1(print_hello, i);
 //join the auxiliary thread with main thread
 thread1.join();
 return 0;
}

Till now, we have created only one thread, apart from the main thread (recall that every program, by default, starts off with a single thread of execution). It is quite straightforward to modify the above piece of code to create a group of threads and invoke them with different functions to execute. I leave that as an exercise for our readers.

Remember, threads in a process share the data associated with that process. Hence, shared data needs to be protected against concurrent accesses from threads. This is typically achieved using explicit synchronisation in the form of locks. For instance, if we have a shared integer counter variable that can be incremented by multiple threads concurrently, we would associate a pthread_mutex to protect the shared counter from concurrent accesses by threads, as shown below:

pthread_mutex_t my_mutex;
int global_counter;
int increment(void)
{
      pthread_mutex_lock(&my_mutex);
      global_counter++;
     pthread_mutex_unlock(&my_mutex);
}

The C++11 standard also provides support for synchronisation between threads using mutexes. For instance, we can implement the locked counter example as follows:

std::mutex my_mutex;
int increment(void)
{
     my_mutex.lock();
     global_counter++;
     my_mutex.unlock();
}

Note that if an exception occurs in the function increment, then the lock associated with my_mutex is not released. In order to ensure the release of locks held when an exception is thrown, we need to use the lock_guard class introduced in C++11, which we will discuss in next month’s column.

My ‘must-read book’ for this month

The suggestion for this month’s must-read book comes from our reader Sajan Kumar, who recommended the book C++ Concurrency in Action by Anthony Williams. As Sajan points out, “C++ introduces a whole bunch of features related to threading, locks and synchronisation. If you want to write concurrent code in C++11, this is definitely the book of your choice.” Thank you, Sajan, for your recommendation.

I also wanted to direct our readers to some interesting presentations on C++11. A set of interesting talks on C++11 by Bjarne Stroustrup, Herb Sutter, Hans Boehm, etc., were delivered at the C++11 event conducted by Microsoft recently. These talks are available on MSDN. There is also a C++11 concurrency tutorial available on Bartosz Milewski’s blog, which is definitely worth listening to. Another useful source of information on concurrency in C++ and on the issues with threads and shared variables is Hans Boehm’s Web page.

If you have a favourite programming book/article that you think is a must-read for every programmer, please do send me a note with the book’s name, and a short write-up on why you think it is useful, so I can mention it in this column. This would help many readers who want to improve their coding skills.

If you have any favourite programming puzzles that you would like to discuss on this forum, please send them to me, along with your solutions and feedback, at sandyasm_AT_yahoo_DOT_com. Till we meet again next month, happy programming and here’s wishing you the very best!

Feature image courtesy: Nisha A. Reused under terms of CC-BY 2.0 License.

All published articles are released under Creative Commons Attribution-NonCommercial 3.0 Unported License, unless otherwise noted.
Open Source For You is powered by WordPress, which gladly sits on top of a CentOS-based LEMP stack.

Creative Commons License.