I'm putting together a Unix/Linux semi-portable thread class (i.e. using the pthread library) for a project I'm working on. Part of the project requires the ability to set the priority of certain threads to allow other threads in the same process more CPU time; which is where the pthread_setschedparam function comes in and my class hits a brick wall.
Below is a simple test I put together to illustrate my issue:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
#include <string.h>
#include <errno.h>
pthread_mutex_t m_mtx;
bool m_goahead;
void dosleep(int millis)
{
usleep(millis*1000);
}
void domsg(const char *msg)
{
pthread_mutex_lock(&m_mtx);
std::cout << msg << std::endl;
pthread_mutex_unlock(&m_mtx);
}
void dowait() {
while (!m_goahead) {
dosleep(1);
}
}
void *fn1(void *param)
{
domsg("in fn1...waiting");
dowait();
while (m_goahead) {
dosleep(1000);
domsg("in fn1 loop");
}
}
void *fn2(void *param)
{
domsg("in fn2...waiting");
dowait();
while (m_goahead) {
dosleep(1000);
domsg("in fn2 loop");
}
}
int main(int argc, char **argv)
{
// min prio = -2, max prio = 2
int t1_pri = 2, t2_pri = 0, main_pri = 1;
//SCHED_RR, SCHED_FIFO, SCHED_OTHER (POSIX scheduling policies)
int sched = SCHED_OTHER; // standard
// get the range between min and max and set the priorities base on split range
int min = sched_get_priority_min(sched);
int max = sched_get_priority_max(sched);
int skip = (max - min) / 5; // 5 since -2...2
struct sched_param main_param, t1_param, t2_param;
memset(&main_param, 0, sizeof(sched_param));
memset(&t1_param, 0, sizeof(sched_param));
memset(&t2_param, 0, sizeof(sched_param));
main_param.sched_priority = (min + ((main_pri+2) * (skip+1))) + (skip / 2);
t1_param.sched_priority = (min + ((t1_pri+2) * (skip+1))) + (skip / 2);
t2_param.sched_priority = (min + ((t2_pri+2) * (skip+1))) + (skip / 2);
std::cout << "main thread will have a prio of " << main_param.sched_priority << std::endl;
std::cout << "t1 thread will have a prio of " << t1_param.sched_priority << std::endl;
std::cout << "t2 thread will have a prio of " << t2_param.sched_priority << std::endl;
m_goahead = false;
pthread_mutex_init(&m_mtx, NULL);
pthread_t t1, t2;
// Create the threads
if (pthread_create(&t1, NULL, fn1, NULL) != 0) {
std::cout << "couldn't create t1" << std::endl;
return -1;
}
if (pthread_create(&t2, NULL, fn2, NULL) != 0) {
std::cout << "couldn't create t2" << std::endl;
return -1;
}
dosleep(1000); // sleep a second before setting priorities
// --main thread--
if (pthread_setschedparam(pthread_self(), sched, &main_param) != 0) {
std::cout << "error setting priority for main thread: (" << errno << "), " << strerror(errno) << std::endl;
}
// --t1 thread--
if (pthread_setschedparam(t1, sched, &t1_param) != 0) {
std::cout << "error setting priority for T1: (" << errno << "), " << strerror(errno) << std::endl;
}
// --t2 thread--
if (pthread_setschedparam(t2, sched, &t2_param) != 0) {
std::cout << "error setting priority for T2: (" << errno << "), " << strerror(errno) << std::endl;
}
m_goahead = true; // all start
// loop until user interupt
for (;;) {
dosleep(1000);
domsg("in main loop");
}
pthread_mutex_destroy(&m_mtx);
return 0;
}
Base on this code, if I compile this and run it on an OpenBSD system, I get the following:
main thread will have a prio of 24
t1 thread will have a prio of 31
t2 thread will have a prio of 17
in fn1...waiting
in fn2...waiting
in fn1 loop
in main loop
in fn2 loop
in fn1 loop
in main loop
in fn2 loop
in fn1 loop
in main loop
in fn2 loop
Note how it goes in the order of the thread priority, fn1, main, fn2...
If I run this same test on an Ubuntu 10.04LTS system, I get the following:
main thread will have a prio of 3
t1 thread will have a prio of 4
t2 thread will have a prio of 2
in fn1...waiting
in fn2...waiting
error setting priority for main thread: (22), Invalid argument
error setting priority for T1: (22), Invalid argument
error setting priority for T2: (22), Invalid argument
in main loop
in fn2 loop
in fn1 loop
in main loop
in fn2 loop
in fn1 loop
in main loop
in fn2 loop
in fn1 loop
I understand that the invalid argument is because I'm specifying the SCHED_OTHER priority class and trying to assign it any number other than 0; what I cannot figure is how can I make this work properly?
I've tried 'assuming' a SCHED_FIFO or SCHED_RR priority class to get the min/max values, which gives me valid min/max values and I don't get the 'invalid argument' error, but the function loop output is not in the prioritized order, it is instead in whatever order the function happened to get called (to be expected if no priority was set).
Ideally I would get the current process' priority class, then assign the thread on that class too, however, if the current process' priority is SCHED_OTHER then setting a thread based on that produces the invalid results I don't want.
Is there a more 'portable' way to set a thread's priority or grab the valid min/max values? Can I even set a thread's priority under SCHED_OTHER in certain environments, or is that functionality left to said environment?
I'm at an impasse with this problem and would appreciate any insight or pointers in the right direction.
Thanks and please let me know if my code/explanation is unclear.
Refer this if it helps increase understanding. But what little I learned, SCHED_OTHER is their only to mean that all the non real time thread will have equal priority. But then in the question referred BSD give max and min 0 and 99 even in case of SCHED_OTHER, didn't understand why, but one thing is clear, it is not a very portable and to rely on its exact value will not help. In that case putting special handling will do much better, like if range is [0-0] then using nice(if thread priority can be fixed by nice,please let me know) be used to set the priority.
Thanks
Related
I am somewhat new to threads but have been reading up on pthreads and trying to write a test program that will eventually be used in an application. The basic operation for this test app is to:
spawn two threads,
allow a user to enter a number (sender thread) that is stored in a value of a conditioned variable and then use a signal to wake up a second thread (receiver thread).
I am showing the code and the output screen to show the results of the test output.
Basically, when I enter a number other than 0 the signal does not wake up the second thread. When a 0 is entered the second thread wakes up and performs as expected (i.e. exits).
The question is why does the second thread not wake up for other numbers (i.e. 1 or 2). I have tried this with both a pthread_cond_timedwait() and pthread_cond_wait() but I get the same results in either case.
I would appreciate any and all suggestions. Thanks.
//============================ terminal screen =================================
enter a message to send:
0 - exit
1 - message 1
2 - message 2
1
messageSender: msg.value: 1
messageSender: signal sent!
2
messageSender: msg.value: 2
messageSender: signal sent!
0
messageSender: msg.value: 0
messageSender: signal sent!
exit pthread_cond_timedwait, testVal: 1 value: 0
0
messageReceiverThread msg.value received: 0
exiting
//================= apps/pthread_com_example.h =================================
#ifndef PTHREAD_COM_EXAMPLE_H_INCLUDED
#define PTHREAD_COM_EXAMPLE_H_INCLUDED
// | added for thread com example RLB 04Dec2021
// V
#if(defined PTHREAD_COM_EXAMPLE)
#include <stdio.h> // standard I/O routines
#include <stdlib.h> // rand() and srand() functions
// number of threads used to service requests
#define NUM_HANDLER_THREADS 2
/* format of a message structure */
typedef struct {
pthread_mutex_t mutex; // message mutex
pthread_cond_t cond; // message condition variable
int testVal; // use this as the predicate
int value; // value to be passed
}message_t;
// ^
// | added for thread com example RLB 04Dec2021
#endif
#endif // PTHREAD_COM_EXAMPLE_H_INCLUDED
//=================== pthread_com_example app ==============================
#include <iostream>
#include <pthread.h>
#define PTHREAD_COM_EXAMPLE
#include <apps/pthread_com_example.h>
#include <unistd.h>
#include <sys/time.h> // struct timeval definition
#if(defined PTHREAD_COM_EXAMPLE)
/* global mutex for example. assignment initializes it.
note: a RECURSIVE mutex is used, since a handler
thread might try to lock it twice consecutively. */
#define MSG1 1 // trigger messasge 1
#define MSG2 2 // trigger message 2
#define MSGX 0 // message to exit
std::string message1 = "this is an example message in response to A";
std::string message2 = "this is an example message in response to B";
struct timeval now;
struct timespec timeout;
message_t msg = {
// initialize the message structure.
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, PTHREAD_COND_INITIALIZER, 0, 0
};
#endif
#if(defined PTHREAD_COM_EXAMPLE)
#define DEBUG
// wait for sending thread to signal variable is ready to read
int timedWaitForCond(pthread_cond_t* pCond, pthread_mutex_t* pMutex, int timeToWait)
{
gettimeofday(&now,NULL);
timeout.tv_sec = now.tv_sec + timeToWait;
timeout.tv_nsec = now.tv_usec * 1000;
int retcode = 0;
while((msg.testVal == 0) && (retcode != ETIMEDOUT))
{
// this unlocks the mutux while waiting on signal from another thread
// retcode = pthread_cond_timedwait(pCond, pMutex, &timeout);
retcode = pthread_cond_wait(pCond,pMutex);
// upon return the mutex is locked again
std::cout << "exit pthread_cond_timedwait, testVal: " << msg.testVal << " value: " << msg.value << std::endl;
if(retcode != 0)
{
switch(retcode)
{
case ETIMEDOUT: // exits while loop
std::cout << "Timed Conditional Wait, Timed Out" << std::endl;
return(-1); // conditioned variable is not ready yet
break;
default: // stays in while loop
std::cout << "Unexpected timed conditional wait result: " << retcode << std::endl;
break;
}
}else
{
return(0); // conditioned variable ready to read
break; // exit while if legitimate signal occurred
}
}
return(0);
}
void outputMenu(void)
{
std::cout << std::endl << std::endl;
std::cout << "enter a message to send: " << std::endl;
std::cout << " 0 - exit" << std::endl;
std::cout << " 1 - message 1" << std::endl;
std::cout << " 2 - message 2" << std::endl;
}
int getUserMessage(void)
{
int rc;
std::cin >> rc;
return(rc);
}
/*
* function messageReceiverThread():
* attempts to wait for message wait. If result is good
* sends signal to waiting thread and then performs a
* timed conditioned wait to receive the message from the
* sending thread.
*
* algorithm:
* input: messageWait mutex
* output: character from message_t structure
* memory: shared message_t structure
*/
void* messageReceiverThread(void* arg)
{
int timeToWait = 10; // use 10 second timeout for this example
int rc = 0;
// first, lock the mutex, to assure exclusive access to the conditioned variable
rc = pthread_mutex_lock(&msg.mutex);
if (rc)
{ // an error has occurred
std::cout << "messageReceiver: pthread_mutex_lock error: " << rc << std::endl;
pthread_exit(NULL);
}
// mutex is now locked
outputMenu();
while(1)
{
msg.testVal = 0; // clear signal value
rc = timedWaitForCond( &msg.cond , &msg.mutex, timeToWait); // cond variable is unlocked while waiting
// var is locked again after exit from timedWaitForCond()
if(0 != rc)
{
// Timeout or error, no messasge yet, for this app just loop around...
std::cout << "no message yet, rc = " << rc << std::endl;
}else
{
// return from timed wait with valid signal from sender
std::cout << msg.value << std::endl;
std::cout << "messageReceiverThread msg.value received: " << msg.value << std::endl;
switch(msg.value)
{
case MSG1:
std::cout << message1 << std::endl;
break;
case MSG2:
std::cout << message2 << std::endl;
break;
case MSGX:
std::cout << "exiting" << std::endl;
pthread_exit(arg);
break;
default:
std::cout << "unrecognized message" << std::endl;
break;
}
outputMenu();
}
}
pthread_exit(arg);
}
/*
* function messageSenderThread():
* attempts to wait for user to enter a number at the console.
* When the number is received this thread will attempt a lock
* on the message mutex and, once obtained, write the value
* to the msg.msgNbr. Then this thread will send a signal to
* the messageReceiver thread. Once completed this thread will
* again wait on the user for an additional imput, until the
* value enterd by the user is 0, which will cause an exit.
*
* algorithm:
* input: messageWait mutex
* output: character from message_t structure
* memory: shared message_t structure
*/
void* messageSenderThread(void* arg)
{
int rc = 0,msgCode = 0;
while(1)
{
rc = pthread_mutex_lock(&msg.mutex);
if(rc)
{
std::cout << "messageSender: lock request failed,result: " << rc << std::endl;
pthread_exit(arg);
}
msgCode = getUserMessage();
msg.value = msgCode;
msg.testVal = 1;
std::cout << "messageSender: msg.value: " << msg.value << std::endl;
// signal the condition variable - there's a new message to handle
rc = pthread_cond_signal(&msg.cond);
std::cout << "messageSender: signal sent! " << std::endl;
if(rc)
std::cout << "messageSender: pthread_cond_signal failed,result: " << rc << std::endl;
// unlock mutex
rc = pthread_mutex_unlock(&msg.mutex);
if(rc)
{
std::cout << "messageSender: unlock request failed,result: " << rc << std::endl;
pthread_exit(arg);
}
if(!msgCode) // user entered the exit code
pthread_exit(arg);
}
pthread_exit(arg);
}
#undef DEBUG
#endif
int main(int argc, char *argv[])
{
#if(defined PTHREAD_COM_EXAMPLE)
int thr_id[NUM_HANDLER_THREADS]; // thread IDs
pthread_t thread1,thread2; // thread's structures
// create the message handling threads
thr_id[0] = 0;
(void) pthread_create(&thread1, NULL, messageReceiverThread, (void*)&thr_id[0]);
thr_id[1] = 1;
(void) pthread_create(&thread2, NULL, messageSenderThread, (void*)&thr_id[1]);
// now wait for threads to exit...
(void) pthread_join(thread1, NULL);
(void) pthread_join(thread2, NULL);
return 0;
#endif
}
The problem here is starvation. Your messageSenderThread() function keeps the mutex locked practically all the time. Each time it releases the mutex at the bottom of the loop, the very next thing it does is (if you don't type "0") it re-locks the mutex back at the top of the loop. The messageReceiverThread() always loses the race to lock the mutex.
The reason why the message receiver does print something after you enter the "quit" command (0), is because the messageSenderThread() unlocks the mutex and it exits after you enter a zero. That finally allows the receiver to lock the mutex and do its thing.
I want a thread to run infinity times in order to execute the task described on do_work() function that it receives. However, the function is only called on the pthread_create() subroutine.
I've tried to implement the sched_yield() and the pthread_join() routines on a while loop. But it didn't work yet.
Is there any routine in which I can call the existing thread again?
int main (int argc, char ** argv) {
int period;
int priority;
int load;
char schedule[15];
period = atoi(argv[1]);
priority = atoi(argv[2]);
load = atoi(argv[3]);
strncpy(schedule,argv[4],100);
std::cout << " period : " << period <<"\n priority : "<< priority << "\n load : "<< load << "\n schedule : " << schedule <<std::endl;
struct sched_param param;
pthread_t thread;
int rc;
sched_setscheduler (0, SCHED_FIFO , ¶m);
std::cout << "main() : creating thread " << std::endl;
rc = pthread_create(&thread, NULL, do_work, (void*)load);
if (rc) {
std::cout << "Error:unable to create thread " << rc << std::endl;
exit(-1);
}
int i=0;
struct sigaction action;
struct itimerval timer;
while(i<10000){
pthread_join(thread, NULL);
sched_yield();
i++;
}
pthread_exit(NULL);
}
You do not call a thread, you create a thread. By doing that, you specify a start_routine which will be called 'in' the new thread.
If you want to call repeatedly a function in a loop, then you can do the following in your start_routine:
void* start_routine(void *arg) {
while (active) { // active: atomic global boolean value
do_work();
}
// if no longer active,
// there could be an option to wait to become active again,
// or exit the thread
pthread_exit(NULL);
}
pthread_join() is only called, if you want to join a thread with other thread(s). pthread_join() waits until the target thread has terminated. By joining the thread, all resources are given back to the system (cleanup).
Just wanted to show you the code I implemented.
I was not sure about the meaning of Threads when I made this question and you helped me to understand that I cannot access the function on a thread multiple times, but I have to create it on each use.
My main objective was to find a way of calling the do_work() function on the reception of the signal SIGALRM. Thus, I just assumed the do_wordk() to be my Thread and used a sigaction struct to control the arrival of the signal.
If you guys want to test the code, it returns the execution time of the do_work() function and a message if the deadline set on your period was lost. The purpose of this work was to make an analogy with periodic threads.
To compile:
g++ teste.cpp -o exe -lrt
To run:
sudo taskset -c 0 ./exe 300 1 100000 F
sudo taskset -c 0 ./exe Period Priority Work_Load Scheduller_Policy
#include<signal.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<sys/time.h>
#include<iostream>
#include<string>
#include<string.h>
long load=1;
void deadline();
void do_work();
void wakeup(int j){
struct itimerval aux;
int t1, t2;
getitimer( ITIMER_REAL, &aux); //Get initial resume time
t1 = aux.it_value.tv_usec;
//std::cout << "Hello World! Thread working |Resume Time : " <<t1<< std::endl;
do_work();
getitimer( ITIMER_REAL, &aux);
t2 = aux.it_value.tv_usec; //Get Final resume time
std::cout << "Execution time (usec): " <<t1 - t2<< std::endl;
if (t2==0){
deadline();
}
return;
}
void do_work(){
for ( int i = 0; i < load * 1000; i++) {
/* do nothing , keep counting */
}
}
void deadline() {
std::cout << "Lost deadline!" << std::endl;
}
int main (int argc, char ** argv) {
int i;
int period;
int priority;
char scheduler[5];
period = atoi(argv[1])*1000;
priority = atoi(argv[2]);
load = atoi(argv[3]);
strcpy(scheduler, argv[4]);
std::cout << " period : " << period <<"\n priority : "<< priority << "\n load : "<< load << "\n scheduler : " << scheduler <<std::endl;
struct sched_param param;
param.sched_priority = priority;
if (scheduler[0]=='F'){
int r = sched_setscheduler (0, SCHED_FIFO , ¶m);
if(r==-1){ perror("scheduller"); return 1;}
std::cout <<"FIFO scheduller: "<<r<<std::endl;
}else{
int r = sched_setscheduler (0, SCHED_RR , ¶m);
if(r==-1){ perror("scheduller"); return 1;}
std::cout <<"RR scheduller: "<<r<<std::endl;
}
struct itimerval val;
struct sigaction action;
sigset_t mask;
sigemptyset(&action.sa_mask);
action.sa_handler = wakeup;
action.sa_flags=SA_RESTART;
if(sigaction(SIGALRM, &action, 0)==-1){
perror("sigaction");
return 1;
}
val.it_interval.tv_sec=0;
val.it_interval.tv_usec=period;
val.it_value.tv_sec=0;
val.it_value.tv_usec=period;
if(setitimer(ITIMER_REAL, &val, 0)==-1){
perror("setitimer");
return 1;
}
if(sigwait( &mask, &i)==-1){
perror("sigwait");
}
return 0;
}
Finally, I am really grateful for your patience in understanding my problem. This is my first question on this community and I hope I'll improve them over time. Thank you all for your answers and the effort on helping me.
In this program, I'm trying to print my username and then create two threads. I want each thread to print its thread id and go into a loop and display something periodically.
Here is the code I have
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <string>
void task(std::string threadNum)
{
std::thread::id this_id = std::this_thread::get_id();
std::cout << threadNum << " : " << this_id << std::endl;
for(int i=0; i<1000; i++){
if(i == 300 or i == 600 or i == 900){
std::cout << threadNum << " has reached step: " << i << std::endl;
}
}
}
int main()
{
std::cout << "Your Username Is: " << getenv("USER") << std::endl;
std::thread t1(task, "Thread 1");
std::thread t2(task, "Thread 2");
t1.join();
t2.join();
}
and I get different outputs every single time I run the program, for example
Your Username Is: gansaikhanshur
Thread 2 : Thread 1 : 0x70000741e000
Thread 2 has reached step: 0x70000739b000300
Thread 1 has reached step: 300
Thread 2 has reached step: 600
Thread 2 has reached step: 900
Thread 1 has reached step: 600
Thread 1 has reached step: 900
Thread1 and Thread 2 does not show it's thread ID as it should. Why do I get different results all the time? and Is it possible for me to make thread 1 and thread 2 to display their correct thread ids?
This is the way threads work -- they run independently and any side-effects they have may show up interleaved in any order. If you want to ensure that doesn't happen, you need to synchronize. For example, if you want to ensure that lines written to cout don't get mixed up, you can lock around each line output:
std::mutex cout_lock;
void task(std::string threadNum)
{
std::thread::id this_id = std::this_thread::get_id();
{
std::lock_guard<std::mutex> lock(cout_lock);
std::cout << threadNum << " : " << this_id << std::endl;
}
for(int i=0; i<1000; i++){
if(i == 300 or i == 600 or i == 900) {
std::lock_guard<std::mutex> lock(cout_lock);
std::cout << threadNum << " has reached step: " << i << std::endl;
}
}
}
lock_guard gives you a nice easy exception-safe way to manage lock/unlock operations.
I'm trying to figure out how to use std::condition_variable in C++ implementing a "strange" producer and consumer program in which I had set a limit to the count variable.
The main thread ("producer") increments the count and must wait for this to return to zero to issue a new increment.
The other threads enters in a loop where they have to decrease the counter and issue the notification.
I am blocked because it is not clear to me how to conclude the program by orderly exiting the while loop inside the function of all threads.
Could someone give me some guidance on how to implement it, please?
Code
#include <iostream>
#include <thread>
#include <condition_variable>
#include <vector>
int main() {
int n_core = std::thread::hardware_concurrency();
std::vector<std::thread> workers;
int max = 100;
int count = 0;
std::condition_variable cv;
std::mutex mutex;
int timecalled = 0;
for (int i = 0; i < n_core; i++) {
workers.emplace_back(std::thread{[&max, &count, &mutex, &cv]() {
while (true) {
std::unique_lock<std::mutex> lk{mutex};
std::cout << std::this_thread::get_id() << " cv" << std::endl;
cv.wait(lk, [&count]() { return count == 1; });
std::cout << std::this_thread::get_id() << " - " << count << std::endl;
count--;
std::cout << std::this_thread::get_id() << " notify dec" << std::endl;
cv.notify_all();
}
}});
}
while (max > 0) {
std::unique_lock<std::mutex> lk{mutex};
std::cout << std::this_thread::get_id() << " cv" << std::endl;
cv.wait(lk, [&count]() { return count == 0; });
std::cout << std::this_thread::get_id() << " created token" << std::endl;
count++;
max--;
timecalled++;
std::cout << std::this_thread::get_id() << " notify inc" << std::endl;
cv.notify_all();
}
for (auto &w : workers) {
w.join();
}
std::cout << timecalled << std::endl; // must be equal to max
std::cout << count << std::endl; // must be zero
}
Problem
The program doesn't end because it is stuck on some final join.
Expected Result
The expected result must be:
100
0
Edits Made
EDIT 1 : I replaced max > 0 in the while with a true. Now the loops are unbounded, but using the solution of #prog-fh seems to work.
EDIT 2 : I added a variable to check the result in the end.
EDIT 3: I changed while(true) to while(max >0). Could this be a problem in concurrency because we are reading it without a lock?
The threads are waiting for something new in the call cv.wait().
But the only change that can be observed with the provided lambda-closure is the value of count.
The value of max must be checked too in order to have a chance to leave this cv.wait() call.
A minimal change in your code could be
cv.wait(lk, [&max, &count]() { return count == 1 || max<=0; });
if(max<=0) break;
assuming that changes to max always occur under the control of the mutex.
An edit to clarify around the accesses to max.
If the loop run by the threads is now while(true), then the max variable is only read in its body which is synchronised by mutex (thanks to lk).
The loop run by the main program is while (max > 0): max is read without synchronisation here but the only thread that can change this variable is the main program itself, so it's pure serial code from this perspective.
The whole body of this loop is synchronised by mutex (thanks to lk) so it is safe to change the value of max here since the read operations in the threads are synchronised in the same way.
You're having race conditions: in your code max may be read by multiple threads, whilst it is being modified in main, which is a race condition according to C++ standard.
The predicates you are using in wait seems to be incorrect (you're using ==).
I am new to multi thread programming, so this question might seem a little silly, but I really need to work this out so I can apply it to my project (which is way more complicated).
Follow is my code, I am trying to have 2 threads (parent and child) to update the same shared timer as they execute and stop when the timer reaches a specific limit.
But when I compile and execute this follow piece of code, there are 2 different outcomes: 1. child prints "done by child at 200000" but the program does not exit; 2. after child prints "done by child at 200000" and exits, parent keeps executing, prints a couple of dozen lines of "parent doing work" and "parent at 190000", then prints "done by parent at 200000" and the program exits properly.
The behavior I want is for whichever thread that updates the timer, hits the limit and exits, the other thread should stop executing and exit as well. I think I might be missing something trivial here, but I've tried changing the code in many ways and nothing I tried seem to work. Any help will be much appreciated :)
#include <iostream>
#include <unistd.h>
#include <mutex>
#include <time.h>
using namespace std;
mutex mtx;
int main () {
int rc;
volatile int done = 0;
clock_t start = clock();
volatile clock_t now;
rc = fork();
if (rc == 0) { //child
while (true) {
cout << "child doing work" << endl;
mtx.lock();
now = clock() - start;
if (done) {
mtx.unlock();
break;
}
if (now >= 200000 && !done) {
done = 1;
cout << "done by child at " << now << endl;
mtx.unlock();
break;
}
cout << "child at " << now << endl;
mtx.unlock();
}
_exit(0);
}
else { // parent
while (true) {
cout << "parent doing work" << endl;
mtx.lock();
now = clock() - start;
if (done) {
mtx.unlock();
break;
}
if (now >= 200000 && !done) {
done = 1;
cout << "done by parent at " << now << endl;
mtx.unlock();
break;
}
cout << "parent at " << now << endl;
mtx.unlock();
}
}
return 0;
}
Multi-processes
Your code is multi-processes and not multi-threading: fork() will create a new separate process by duplicating the calling process.
The consequence: At the moment of the duplication, all the variables contain the same value in both processes. But each process has its own copy, so a variable modified in the parent will not be updated in the child's address space an vice-versa.
If you want to share variables between processes, you should have a look at this SO question
Multithread
For real multithreading, you should use std::thread. And forget about volatile, because it's not thread safe. Use <atomic> instead, as explained in this awesome video.
Here a first try:
#include <iostream>
#include <mutex>
#include <thread>
#include <atomic>
#include <time.h>
using namespace std;
void child (atomic<int>& done, atomic<clock_t>& now, clock_t start)
{
while (!done) {
cout << "child doing work" << endl;
now = clock() - start;
if (now >= 2000 && !done) {
done = 1;
cout << "done by child at " << now << endl;
}
cout << "child at " << now << endl;
this_thread::yield();
}
}
void parent (atomic<int>& done, atomic<clock_t>& now, clock_t start)
{
while (!done) {
cout << "parent doing work" << endl;
now = clock() - start;
if (now >= 2000 && !done) {
done = 1;
cout << "done by parent at " << now << endl;
}
cout << "parent at " << now << endl;
this_thread::yield();
}
}
int main () {
atomic<int> done{0};
clock_t start = clock();
atomic<clock_t> now;
thread t(child, std::ref(done), std::ref(now), start); // attention, without ref, you get clones
parent (done, now, start);
t.join();
return 0;
}
Note that you don't need to protect atomic accesses with a mutex, and that if you want to do, lock_guard would be recommended alternative.
This example is of course rather weak, because if you test an atomic variable if the if-condition, it's value might already have changed when entering the if-block. This doesn't cause a problem in your logic where "done" means "done". But if you'd need a more cauthious approach,
compare_exchange_weak() or compare_exchange_strong() could help further.