Thread

02 Mar 2013

The goal of developing a robust, well performing application, may often require scheduling some of the computational work to be done in a background thread. Every program is started with at least one function on one thread, usually main(). While C++ does not conceptually have a “main thread”, some platforms such as iOS will have a main thread in which UI updates and other event driven actions happen. In many cases we want to offload some other work away from the starting or main thread and into the background so the main thread can continue it’s work. Lets say we are writing software for a bank ATM. The main thread should be responsible for accepting user input and displaying information on the screen. While this is happening, advertisements for other bank products will show up on various screens. The advertisement images are cached but are updated once daily. In order to update the information, we do not want to interrupt the performance of the main banking functions; they should run smoothly and respond quickly, while the less important operations of updating advertisements happen on a separate background thread.

std::thread is an easy way to spawn new threads in C++. Depending on the platform, for example, on OS X, the underlying implementation of this class uses pthreads. If you have used pthreads, the terminology is similar for the std::thread class. Setting up a thread is a simple as:

#include <thread>

void updateInfo(); //function to be called on separate thread 
std::thread thread(updateInfo);

If you want the ability for the initial thread to wait until the spawned thread completes, use the join() function to start the thread. Otherwise you can start the thread with detach(), which basically means the thread runs on it’s own, independent of other threads waiting for it’s completion. A detached thread can not be joinable once it is set to detached.

thread.detach();

In fact you can test if a thread is joinable or not

assert( !thread.joinable() );

In order to pass in arguments to a thread, you simply append the arguments to the thread object like so

void someFunction(std::string myString);

std::thread thread(someFunction, std::string(“hello”));

thread.join();

Arguments are copied when they are passed to a thread. If you really want to send a reference to the thread, wrap it in a reference object

void someFunction(std::string const& theString);

std::string someString(“hello”);

std::thread thread(someFunction, std::ref(someString));

Make sure you know the objects that you pass in as a reference will not be destroyed before your thread is finished with them. Use the const keyword when passing a reference if you are sure you do not want the object being changed by the called function.

All threads have an identifier of type std::thread::id. You can call get_id() on a particular thread to get it’s id, or you can call std::this_thread::get_id() to get the id of the current thread you are in.

If you would like to start a thread that calls a member function of a class, a few more parameters are needed. For our bank example, lets say in our class we have a member function, getUpdatedScreenAdvertisments(), that will call screenAdvertismentNetworkRequest() on a separate thread.

class BankATMManager
{
private:
    void screenAdvertismentNetworkRequest(bool isUserLoggedIn); //called on separate thread
public:
    void getUpdatedScreenAdvertisments();
};

In order to call a function on a class instance, you will need to give it the member function of the class, and then pass it the instance using *this before you supply any other function arguments.

void BankATMManager::getUpdatedScreenAdvertisments()
{
    bool isUserLoggedIn = false;
    std::thread thread = std::thread(&BankATMManager::screenAdvertismentNetworkRequest, *this, isUserLoggedIn);
    thread.detach();
}

Now the following function can be used correctly

void BankATMManager::screenAdvertismentNetworkRequest(bool isUserLoggedIn)
{
    ; //start request...
}

Lastly, if you are dealing with portable code and working on iOS or Mac OS X, there are a few other things to keep in mind. If the code mixes Foundation/Cocoa objects, then every thread you spawn should get it’s own autorelease pool:

void BankATMManager::screenAdvertismentNetworkRequest(bool isUserLoggedIn)
{
   @autoreleasepool
   {
      //NSString *string...
   }
}

Since that particular environment is run-loop based, there is the concept of the main thread where all UI updates are done. Even from a detached thread, to perform any work back on the main thread you can use Grand Central Dispatch to accomplish that task

void BankATMManager::screenAdvertismentNetworkRequest(bool isUserLoggedIn)
{
	//do a bunch of synchronous tasks
	
	//tasks are done
	
	dispatch_async(dispatch_get_main_queue(), ^
	{
	    this->updateUIWithSomeData(someData);
	});
}

With this platform, we have pthreads if you are writing purely in C, NSThread, NSOperation and Grand Central Dispatch if working with Cocoa, and std::thread if working in C++. For more information about std::thread, check here.