×

Search anything:

std::thread vs pthread

Binary Tree book by OpenGenus

Open-Source Internship opportunity by OpenGenus for programmers. Apply now.

In this article, we have explained the concept behind the two popular multi-threading library in C and C++ namely pthread and std::thread and covered the differences between them in depth.

Table of contents

  1. Definitions
  2. std::thread
  3. pthread
  4. Differences table (std::thread vs pthread)

1. Definitions

A thread of execution or simply a thread is a set of instructions that can be run in parallel and independenly of each other while sharing the same resource.

Ex. having a variable count we can achive counting up to 100 in different ways:
1. by writting an increment statement 100 times

      #1 count++;
      #2 count++;
         ...
    #100 count++;

2. by using a loop statement

    #include <iostream>

    using namespace std;

    int main()
    {
        int count;

        for(count=0; count<100; count++);

        cout<<count;

        return 0;
    }

3. recursively

        #include <iostream>

        using namespace std;

        void f_count(int &v)
            {
                if(v < 100) v++, f_count( v );
            }

        int main()
        {
            int count = 0;

            f_count(count);

            cout<<count;

            return 0;
        }

4. by using threads

    #include <iostream>
    #include <vector>
    #include <thread>

    using namespace std;

    void f_count(int &v)
        {
            v++;
        }

    int main()
    {
        int count=0;

        thread t1(f_count, ref(count) );
        thread t2(f_count, ref(count) );
        ...
        thread t100(f_count, ref(count) );

        cout<<count;

        return 0;
    }

Notice that we need to declare 100 threads for this !, and each thread needs to wait to complete for the others, in order to increment the counter.
Could we do it in another way ?

2. std::thread

A Thread class is the class that deals with parallel execution in multitasking operating system in standard C++.

Its constructor receives a pointer to a function and the arguments of it, if any. The way the argumets are transmitted to the function is very important. In previous example, f_count receives a reference to a variable v, because we would like to use its address all the time during multiple calls of threads. To tell the thread that our variable is a reference we have used the ref() function that will make our local variable count a reference.
By not doing so, v will become a local variable and its value will be lost when the function will end.

Instead of declaring threads for 100 times, we can use a vector to store the threads.

    #include <iostream>
    #include <vector>
    #include <thread>

    using namespace std;

    void f_count(int &v)
        {
            v++;
        }

    int main()
    {
        int count = 0 , i;

        vector<thread> vt;

        for(i=0; i<100; i++)
            vt.push_back( thread (f_count, ref(count) ) );

        for(i=0; i<vt.size(); i++)
            vt.at(i).join();

        cout<< count;

        return 0;
    }

Output:

100

We can also call a member function of an object.

    #include <iostream>
    #include <vector>
    #include <thread>

    using namespace std;

    class A
    {
        int count;

     public:
        A(){ count = 0; }

        void f_count() { count++; }

        int get_count(){ return count; }
    };

    int main()
    {
        int i;

        A a;

        vector<thread> vt;

        for(i=0; i<100; i++)
            vt.push_back( thread ( &A::f_count, ref(a) ) );

        for(auto &x:vt) x.join();

        cout<< a.get_count();

        return 0;
    }

Output:

100

Notice in this example that f_count is not having even a parameter, and thus because member variable count is acting as a global variable.
Basically, when you would like to use threads you will need to think in calling multiple times a function that will share and change one or more variables.

The loop statement

    for(auto &x:vt) x.join();

or

    for(thread &x:vt) x.join();

is echivalent with the next one

    for(i=0; i<vt.size(); i++)
        vt.at(i).join();

or the next one

    for(i=0; i<vt.size(); i++)
            {
                thread *x = &vt[i];
                x->join();
            }

and is a short form to go through a vector without any constraint.

This is neceesary to signal when a thread has finished execution, and it is done by using the function join, which blocks the execution of the thread until its function call has been completed.

Using &x is imperious required as threads cannot be copied so we would need a reference to that thread stored into a pointer of type thread, as in below statement:

thread *x = &vt[i];

3. pthread

A pthread is a little bit different than a std::thread.
p stands from POSIX which is an acronym for Portable Operating System Interface and represents a family of standards to maintain the compatibility between different operating systems.

Examples of standards:

POSIX.1: Core Services
POSIX.1b: Real-time extensions
POSIX.1c: Threads extensions
POSIX.2: Shell and Utilities

We will need to include the pthread.h header file to have access to all the functions and data types corresponding to pthreads.

    #include <iostream>
    #include <pthread.h>

    using namespace std;

    void* f_count(void *v)
        {
           (*(int*)v)++;
           
           return NULL;
        }

    int main()
    {
        int *count = new int(0);

        pthread_t t1;
        pthread_create( &t1, NULL, &f_count, count);
        
        pthread_t t2;
        pthread_create( &t2, NULL, &f_count, count);
        
        ...
        
        pthread_t t100;
        pthread_create( &t100, NULL, &f_count, count);
        
        cout<<*count;

        return 0;
    }

Using void* f_count(void *v) is imperious required as the signature of the 3rd parameter function pthread_create is void * (*start_routine)(void *)1.

Ofcourse we can use a vector to compress all the 100 pthreads.

    #include <iostream>
    #include <vector>
    #include <pthread.h>

    using namespace std;

    void* f_count(void *v)
        {
           (*(int*)v)++;
           
           return NULL;
        }

    int main()
    {
        int *count = new int(0);

        vector<pthread_t> vt;
        
        pthread_t t;
        
        for(int i=0; i<100; i++)
            vt.push_back(t);
            
        for(auto x:vt)
            pthread_create( &x, NULL, &f_count, count);            

        cout<<*count;

        return 0;
    }

Output:

99

Notice that we have not used &x here, as phtread_t class supports for the copy constructor and the result is 99 !

There are 3 types of synchronisations in POSIX threads.

  1. join
  2. mutex
  3. condition variable

1. A join syncronisation makes a thread to wait for the others to complete, and it can be invoked by the function phtread_join.

    #include <iostream>
    #include <vector>
    #include <pthread.h>

    using namespace std;

    void* f_count(void *v)
        {
           (*(int*)v)++;
           
           return NULL;
        }

    int main()
    {
        int *count = new int(0);

        vector<pthread_t> vt;
        
        pthread_t t;
        
        for(int i=0; i<100; i++)
            vt.push_back(t);
            
        for(auto x:vt)
            pthread_create( &x, NULL, &f_count, count),
            pthread_join( x, NULL);

        cout<<*count;

        return 0;
    }

Output:

100

2. The mutex syncronisation is used to prevent data inconsistences when threads are accessing the same memory area, but the results depends on the order they are executed, which is named a race condition.

    #include <iostream>
    #include <vector>
    #include <pthread.h>

    using namespace std;
    
    pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    
    void* f_count(void *v)
        {
           pthread_mutex_lock(&mutex1);
           (*(int*)v)++;
           pthread_mutex_unlock(&mutex1);
           
           return NULL;
        }

    int main()
    {
        int *count = new int(0);

        vector<pthread_t> vt;

        pthread_t t;
        
        for(int i=0; i<100; i++)
            vt.push_back(t);
            
        for(auto x:vt)
            pthread_create( &x, NULL, &f_count, count),
            cout<<*count<<" ";

        return 0;
    }

Output:

0 0 1 2 3 3 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 23 25 25 26 28 29 30 31 32 33 34 35 36 36 38 39 39 39 40 41 41 41 41 41 41 41 41 41 42 43 44 44 46 46 47 48 49 50 51 52 53 54 55 56 57 58 59 59 60 61 62 63 63 64 65 66 68 69 70 71 72 72 73 74 75 76 77 78 79 79 79 79 79 79 79

The previous output is a one time only, at another runtime you will get another result.

Even thought we have declared 100 threads, using mutex in our routine, makes the variable count to not reach 100 all the time, maybe not even once, so, we will need to rethink our program.

    #include <iostream>
    #include <pthread.h>

    using namespace std;
    
    pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    
    void* f_count(void *v)
        {
           pthread_mutex_lock(&mutex1);
           (*(int*)v)++;
           pthread_mutex_unlock(&mutex1);
           
           return NULL;
        }

    int main()
    {
        int *count = new int(0);
        
        int count_thread = 0;
        
        while( *count < 100)    
            {
                pthread_t t;
                pthread_create( &t, NULL, &f_count, count);
                count_thread++;
            }
        cout<<*count<<endl;
        
        cout<<"No of threads created:"<<count_thread<<endl;    
        
        return 0;
    }

Output 1:

100
No of threads created:139

Output 2:

155
No of threads created:259

Each time we'll run this program we will get another number of threads and time to time a cout number different than 100 ! and thus because not all the threads are completed when the count variable riches 100, which leads to count forward until all the threads are finished. One solution to fix this is to use the join function another one is to place an if condition when we increment the variable.
This might be an exersise for you to do.

3. Condition variable syncronisation is used inside of start_routine to tell the thread when to wait and continue the execution.

    #include <iostream>
    #include <pthread.h>

    #define COUNT_HALT 10

    using namespace std;
    
    pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
    
    pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
    
    void* f_count(void *v)
        {
            while(1)
            {
                pthread_mutex_lock(&mutex2);
                if( (*(int *)v) % 10 == COUNT_HALT )
                    pthread_cond_wait(&cond1, &mutex2);
                pthread_mutex_unlock(&mutex2);
        
                pthread_mutex_lock(&mutex2);
                if( (*(int *)v) % 10 < COUNT_HALT )
                    pthread_cond_signal(&cond1);
                pthread_mutex_unlock(&mutex2);
        
                pthread_mutex_lock(&mutex1);
                if( (*(int *)v) != 100 )
                    (*(int *)v)++;
                pthread_mutex_unlock(&mutex1);
                
                if( (*(int *)v) == 100 ) return NULL;
            }
            return NULL;
        }

    int main()
    {
        int *count = new int(0);
        
        int count_thread = 0;
        
        while( *count < 100)    
            {
                pthread_t t;
                pthread_create( &t, NULL, &f_count, count);
                count_thread++;
            }
        cout<<*count<<endl;
        
        cout<<"No of threads created:"<<count_thread<<endl;    
        
        return 0;
    }

Output 1:

100
No of threads created:5

Output 2:

100
No of threads created:660

The logic behind the previous example was to stop a thread when the counter will be divisible with 10, which is done by the pthread_cond_wait function and to run it when is not divisible by 10 which is done by pthread_cond_signal function. It is imperious to check every time when the counter reaches 100 otherwise we will get wrong results and if it reaches that value to exit the infinite loop.

Differences table (std::thread vs pthread)

pthread vs std::thread (Differences)
std::thread pthread
Header file thread pthread.h
C or C++? C++ only Both C and C++
Cross platform No Yes
Copy constructor No Yes
Routine return value No return value Yes (void *)
Join syncro Yes Yes
mutex syncro No Yes
cond var syncro No Yes
Compilation option No extra flags -lpthread option with GCC

With this article at OpenGenus, you must have the complete idea of std::thread and pthread along with the differences with them.

std::thread vs pthread
Share this