5

The question: Is it possible to guarantee code execution can only occur in one thread at a time in a multi-threaded program? (Or something which approximates this)

Specifically: I have a controller M (which is a thread) and threads A, B, C. I would like M to be able to decided who should be allowed to run. When the thread has finished (either finally or temporarily) the control transfers back to M.

Why: Ideally I want A, B and C to execute their code in their own thread while the others are not running. This would enable each thread to keep their instruction pointer and stack while they pause, starting back where they left off when the controller gives them the control back.

What I'm doing now: I've written some code which can actually do this - but I don't like it.

In pseudo-C:

//Controller M
//do some stuff
UnlockMutex(mutex);
do{}while(lockval==0);
LockMutex(mutex);
//continue with other stuff


//Thread A
//The controller currently has the mutex - will release it at UnlockMutex
LockMutex(mutex); 
lockval=1;
//do stuff
UnlockMutex(mutex);

The reason why

 do{}while(lockval==0);

is required is that when the mutex is unlocked, both A and M will continue. This hack ensures that A won't unlock the mutex before M can lock it again allowing A to retake the lock a second time and run again (it should only run once).

The do-while seems like overkill, but does the job. So my question is, is there a better way?

  • Why do you not want threads A, B, and C running at the same time? What is the problem you're trying to solve? There's almost certainly a better way to solve the problem, but you have to tell us what the problem *is*. – David Schwartz May 05 '12 at 13:47
  • This part confuses me: "This would enable each thread to keep their instruction pointer and stack while they pause". That is not the purpose of mutexes. The operating system (assuming preemptive thread model) maintains each thread's stack and registers as it gives them CPU time. – veefu May 05 '12 at 13:57
  • 1
    @veefu - the OP wants 'green threads', AKA fibers, (I think). A, B and C all get their own stack but only one can actually run at a time because there is only one kernel thread. – Martin James May 05 '12 at 14:04
  • Maybe [`std::condition_variable`](http://en.cppreference.com/w/cpp/thread/condition_variable) is what you want? Anyway, I think David is right and we have a XY problem here – Gunther Piez May 05 '12 at 14:09

3 Answers3

2

Assuming you're running on Windows, you might try looking at Fibers. (See eg http://developer.amd.com/Pages/1031200677.aspx or just google "windows fibers".)

I suspect you're really looking for coroutines.

Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
  • This sounds like exactly what I need. Thanks! – EggplantBonanza May 05 '12 at 14:33
  • 1
    If threads A, B, and C all return to the controller thread M anyway, why not just do away with the threads all together and just call funcA fucnB and funcC from thread M? Is the fiber abstraction really necessary? – veefu May 05 '12 at 16:55
  • @veefu, there is no one single return point from funcA, etc. I also want the function to resume from the point in which it transferred control away. Having implemented fibers, it works exactly how I wanted it to. – EggplantBonanza May 07 '12 at 00:17
1

Check for "CriticalSection" in Win32. C++ 11 uses an other term "lock_guard".

How do I make a critical section with Boost?

http://en.cppreference.com/w/cpp/thread/lock_guard

Your code

do{}while(lockval==0);

will eat up your CPU performance.

Community
  • 1
  • 1
stefan bachert
  • 9,413
  • 4
  • 33
  • 40
0

I presume your are coding c++ under linux and using pthread API. Here is the code, not so much robust, but a good point to start. Hope useful to you. Using "g++ test_controller_thread.cpp -pthread -o test_controller_thread" to make the binary executive.

// 3 threads, one for controller, the other two for worker1 and worker2.
// Only one thread can proceed at any time.
// We use one pthread_mutex_t and two pthread_cond_t to guarantee this.
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_controller_cond = PTHREAD_COND_INITIALIZER;
static pthread_cond_t g_worker_cond = PTHREAD_COND_INITIALIZER;

void* controller_func(void *arg) {
    printf("entering the controller thread. \n");
    // limit the max time the controller can run
    int max_run_time = 5;
    int run_time = 0;
    pthread_mutex_lock(&g_mutex);
    while (run_time++ < max_run_time) {
        printf("controller is waitting.\n");
        pthread_cond_wait(&g_controller_cond, &g_mutex);
        printf("controller is woken up.\n");
        pthread_cond_signal(&g_worker_cond);
        printf("signal worker to wake up.\n");
    }
    pthread_mutex_unlock(&g_mutex);
}

void* worker_func(void *arg) {
    int work_id = *(int*)arg;
    printf("worker %d start.\n", work_id);
    pthread_mutex_lock(&g_mutex);
    while (1) {
        printf("worker %d is waitting for controller.\n", work_id);
        pthread_cond_wait(&g_worker_cond, &g_mutex);
        printf("worker %d is working.\n", work_id);
        pthread_cond_signal(&g_controller_cond);
        printf("worker %d signal the controller.\n", work_id);
    }
    pthread_mutex_unlock(&g_mutex);
}

int main() {
    pthread_t controller_thread, worker_thread_1, worker_thread_2;
    int worker_id_1 = 1;
    int worker_id_2 = 2;
    pthread_create(&controller_thread, NULL, controller_func, NULL);
    pthread_create(&worker_thread_1, NULL, worker_func, &worker_id_1);
    pthread_create(&worker_thread_2, NULL, worker_func, &worker_id_2);

    sleep(1);
    printf("\nsignal the controller to start all the process.\n\n");
    pthread_cond_signal(&g_controller_cond);

    pthread_join(controller_thread, NULL);
    pthread_cancel(worker_thread_1);
    pthread_cancel(worker_thread_2);

    return 0;
}
MagicGup
  • 1
  • 1