How Can We Control/Schedule Execution of Threads in C, C++?
In this article, we dig into a rather low-level topic, how to schedule and control the execution of threads in a program. To do so, we'll use both C and C++.
Join the DZone community and get the full member experience.
Join For FreeDownload source code from: https://github.com/prateekparallel/InterThreadCommunication
The first two examples are in C and the last one is in C++. In my first approach, I am using 3 mutexs and 3 condition variables. With the below examples, you can schedule or control any number of threads in C and C++. First, look at the first thread below. Here it locked mutex lock1 (so that other threads could not access the code) starts executing (code not added, just comments) and finally after completing its task waiting on cond1, likewise, the second thread locked mutex lock2, started executing its business logic, and, finally, waited on conditions from cond2 and the third thread locked mutex lock3, started executing its business logic and finally waited for the condition of cond3.
I am not adding any business logic here because this is just an example. In the sections that are commented out, you can add your business logic which will execute in parallel mode. Suppose thread3 depends on the final output of thread1, which is going to be inserted in a table, and thread3 will read that information before creating its final result, and thread2 depends on the final outcome of thread3 to generate its final outcome. Hence, thread1, after inserting the data into a table, signals thread3 through the condition variable to go ahead with its final process. That means that thread1 controls thread3. As thread2 depends on the final outcome from thread3, thread3 controls the execution of thread2. Here, we can allow thread1 to execute independently as its operation does not depend on any other thread, but, for example, for thread control, we are controlling all the threads here. Hence, thread1 is being controlled from thread2.
To start the controlling process, we are releasing thread1 first. In the main thread (i.e. main function; every program has one main thread, in C/C++ this main thread is created automatically by the operating system once the control passes to the main method/function via the kernel) we are calling pthread_cond_signal(&cond1);
. Once this function is called from the main thread, thread1, which was waiting on cond1, will be released and it will start executing further. Once it finishes its final task, it will call pthread_cond_signal(&cond3);
. Now, the thread which was waiting on the condition of cond3, i.e. thread3, will be released and it will start to execute its final stage and will call pthread_cond_signal(&cond2);
and it will release the thread which is waiting on the condition of cond2, in this case, thread2. This is the way we can schedule and control the execution of threads in a multi-threaded environment.
#include<pthread.h>
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond3 = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock3 = PTHREAD_MUTEX_INITIALIZER;
int TRUE = 1;
void print(char *p)
{
printf("%s",p);
}
void * threadMethod1(void *arg)
{
printf("In thread1\n");
do{
pthread_mutex_lock(&lock1);
//Add your business logic(parallel execution codes) here
pthread_cond_wait(&cond1, &lock1);
printf("I am thread1 generating the final report and inserting into a table \n");
pthread_cond_signal(&cond3);/* Now allow 3rd thread to process */
pthread_mutex_unlock(&lock1);
}while(TRUE);
pthread_exit(NULL);
}
void * threadMethod2(void *arg)
{
printf("In thread2\n");
do
{
pthread_mutex_lock(&lock2);
//Add your business logic(parallel execution codes) here
pthread_cond_wait(&cond2, &lock2);
printf("I am thread2 generating the final report and inserting into a table \n");
pthread_cond_signal(&cond1);
pthread_mutex_unlock(&lock2);
}while(TRUE);
pthread_exit(NULL);
}
void * threadMethod3(void *arg)
{
printf("In thread3\n");
do
{
pthread_mutex_lock(&lock3);
//Add your business logic(parallel execution codes) here
pthread_cond_wait(&cond3, &lock3);
printf("I am thread3 generating the final report and inserting into a table \n");
pthread_cond_signal(&cond2);
pthread_mutex_unlock(&lock3);
}while(TRUE);
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid1, tid2, tid3;
int i = 0;
printf("Before creating the threads\n");
if( pthread_create(&tid1, NULL, threadMethod1, NULL) != 0 )
printf("Failed to create thread1\n");
if( pthread_create(&tid2, NULL, threadMethod2, NULL) != 0 )
printf("Failed to create thread2\n");
if( pthread_create(&tid3, NULL, threadMethod3, NULL) != 0 )
printf("Failed to create thread3\n");
pthread_cond_signal(&cond1);/* Now allow first thread to process first */
sleep(1);
TRUE = 0;/* Stop all the thread */
sleep(3);
/* this is how we join thread before exit from a system */
/*
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);*/
exit(0);
}
In my second approach, I am using a global variable as a controller to control threads. Please examine the below example carefully to see how it has been scheduled/controlled based on a global variable. But the best approach is the first example, the below is just for understanding. Here I don’t need to explain the logic. Just check the “if condition“ inside the while loop.
#include<pthread.h>
int controller = 0;
void print(char *p)
{
printf("%s",p);
}
void * threadMethod1(void *arg)
{
while(1)
{
if(controller == 3)
break;
}
print("I am thread 1st\n");
controller = 1;
pthread_exit(NULL);
}
void * threadMethod2(void *arg)
{
while(1)
{
if(controller == 1)
break;
}
print("I am thread 2nd\n");
controller = 2;
pthread_exit(NULL);
}
void * threadMethod3(void *arg)
{
while(1)
{
if(controller == 0)
break;
}
print("I am thread 3rd\n");
controller = 3;
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid1, tid2, tid3;
int i = 0;
printf("Before creating the threads\n");
if( pthread_create(&tid1, NULL, threadMethod1, NULL) != 0 )
printf("Failed to create thread1\n");
if( pthread_create(&tid2, NULL, threadMethod2, NULL) != 0 )
printf("Failed to create thread2\n");
sleep(3);
if( pthread_create(&tid3, NULL, threadMethod3, NULL) != 0 )
printf("Failed to create thread3\n");
/*
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);*/
sleep(10);
exit(0);
}
Now my third example is in C++. Here I am using the same approach as I have applied in the first C example. If you came directly to this example please read my very first example in C to understand the approach. In the below example, I have developed the code in Visual Studio 2013 and I am not distributing the code into separate headers and CPP files. Also, I've declared and defined all the methods inline, as this is only an example. Also, you might think, 'why am I declaring three classes while the class structure is the same?' Don’t be confused, I am using the same class definitions for example purposes only. Look at the commented lines for the business logic. Here, every class will have different business functions and logic. This example is for three different threads with three different classes; just assume that all the classes are different with different functionalities and business logic.
#include "stdafx.h"//remove this header file if you are compiling with different compiler
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include "windows.h"//remove this header file if you are compiling with different compiler
std::condition_variable _tcond1;
std::condition_variable _tcond2;
std::condition_variable _tcond3;
class SimpleThread1
{
private:
std::mutex _lockprint;
bool isThreadAlive = true;
public:
SimpleThread1(){}
SimpleThread1(SimpleThread1 &st){};
void StartProcessing()
{
std::unique_lock<std::mutex> locker(_lockprint);
//Add your business logic(parallel execution codes) here
_tcond1.wait(locker);
std::cout << "I am thread :1"<<std::endl;
_tcond3.notify_one();
}
void operator()()
{
while (isThreadAlive)
StartProcessing();
}
void stopeThread()
{
isThreadAlive = false;
}
};
class SimpleThread2
{
private:
std::mutex _lockprint;
bool isThreadAlive = true;
public:
SimpleThread2(){}
SimpleThread2(SimpleThread2 &st) {};
void StartProcessing()
{
std::unique_lock<std::mutex> locker(_lockprint);
//Add your business logic(parallel execution codes) here
_tcond2.wait(locker);
std::cout << "I am thread :2"<< std::endl;
_tcond1.notify_one();
}
void operator()()
{
while (isThreadAlive)
StartProcessing();
}
void stopeThread()
{
isThreadAlive = false;
}
};
class SimpleThread3
{
private:
std::mutex _lockprint;
bool isThreadAlive = true;
public:
SimpleThread3(){}
SimpleThread3(SimpleThread3 &st) {};
void StartProcessing()
{
std::unique_lock<std::mutex> locker(_lockprint);
//Add your business logic(parallel execution codes) here
_tcond3.wait(locker);
std::cout << "I am thread :3"<< std::endl;
_tcond2.notify_one();
}
void operator()()
{
while (isThreadAlive)
StartProcessing();
}
void stopeThread()
{
isThreadAlive = false;
}
};
int main()
{
SimpleThread1 st1;
SimpleThread2 st2;
SimpleThread3 st3;
std::thread t1(st1);
std::thread t2(st2);
std::thread t3(st3);
_tcond1.notify_one();
t1.detach();
t2.detach();
t3.detach();
Sleep(1000);//replace it with sleep(10) for linux/unix
st1.stopeThread();
st2.stopeThread();
st3.stopeThread();
return 0;
}
Opinions expressed by DZone contributors are their own.
Comments