views:

62

answers:

5
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define WORK_SIZE 1024
pthread_mutex_t work_mutex;
char work_area[WORK_SIZE];
void *thread_start(void *);
int main() {
pthread_t a_thread;
pthread_mutex_init(&work_mutex,NULL);
pthread_create(&a_thread,NULL,thread_start,NULL);
while(1)
{
pthread_mutex_lock(&work_mutex);
printf("Enter some text\n");
fgets(work_area, WORK_SIZE, stdin);
pthread_mutex_unlock(&work_mutex);
}
return 0;
}

void *thread_start(void *arg)
{
sleep(1);
while(1)
{
pthread_mutex_lock(&work_mutex);
printf("You enetered %d char",strlen(work_area));
pthread_mutex_unlock(&work_mutex);
}
}

When I execute the program, after releasing of the mutex lock in main thread, it again aquires the lock, everytime, before the second thread could aquire the lock. I was expecting that once the main thread would release the lock, the second thread which is already blocked would aquire the lock and start execution before the main.

To be more clear, I am getting such type of output :-

Enter some text
qwerty
Enter some text
asdaf
Enter some text
jkdf 
Enter some text
+2  A: 

It just seems that way to you. You lock before doing data entry in main which is going to be orders of magnitude greater than what it take to output the line. In all that time the other thread will do nothing but block. Your main is going to release the lock and microseconds later acquire it again.

If you do this long enough - maybe thousands of times - you will see it work. But it would be better to just copy the input line in main to a queue or some other piece of memory protect by the lock. Then the other thread would have a chance to get at it.

EDIT:

The general idea is this. My code additions are terrible but should work well enough for illustration.

int main()
{
    pthread_t a_thread;
    pthread_mutex_init(&work_mutex, NULL);
    pthread_create(&a_thread, NULL, thread_start, NULL);

    memset(work_area, '\0', sizeof(work_area));


    char input[WORK_SIZE - 1];

    while (1)
    {
        printf("Enter some text\n");
        fgets(input, WORK_SIZE, stdin);

        pthread_mutex_lock(&work_mutex);
        strcpy(work_area, input);
        pthread_mutex_unlock(&work_mutex);
    }

    return 0;
}

void *thread_start(void *arg)
{
    sleep(1);

    while (1)
    {
        pthread_mutex_lock(&work_mutex);

        if (work_area[0] != '\0')
        {
            printf("You enetered %d char\n", strlen(work_area));
            work_area[0] = '\0';
        }

        pthread_mutex_unlock(&work_mutex);
    }
}
Duck
Could you elaborate a little what do you mean by the following -"But it would be better to just copy the input line in main to a queue or some other piece of memory protect by the lock"
Ashish
What I am trying to ask is :- Suppose we have thread A which has aquired a lock on the mutex and doing some work. At the same time thread B, C and D also tried to take lock in that order on the same mutex. They will all get blocked. Now, if A relaeses the lock and then again tries to get lock which one of the all would get the lock next? Does'nt the thread B should get the lock as it was the 1st thread which tried to aquire the lock?
Ashish
You can't reliably predict which thread will acquire the lock, you are at the mercy of the OS. In your code you are giving the print thread a tiny window (micro seconds) in which it could acquire the lock. It eventually will get control but it could take a long, long time to hit the precise window.
Duck
You explained very prcisely about the tiny window problem. Thanks. Now it is giving the expected output.
Ashish
+1  A: 

You may want to create and initialise a semaphore and then wait in the 2nd thread for the main function to signal a event when a input is fed to it.

Check conditional wait and semaphores.

The second thread doesnt know what event is generated in the main thread. For your reference.

Praveen S
I agree that semaphor would be a better way to solve this problem.But can't it be achieved by using Mutexes? The question is not whether to use semaphore or mutex. I want to know why the already blocked 2nd thread does not get control before the main thread when the main thread releases the lock.
Ashish
I'd agree. A semaphore is generally a great fit for producer consumer style problems. It can combine synchronisation and queue depth management into a single abstraction.
torak
+1  A: 

Putting asside the suggestion to use a semaphore, I think that the reason for the behaviour that you are observing is as follows.

  1. Until the pthread_mutex_lock in thread_start is called, the loop in main won't be blocked.
  2. Therefore, the only time that thread_start's loop will get a chance to call pthread_mutex_lock is when the time slice of the thread executing main expires
  3. The chances of that time slice expiry occuring while the lock is released is miniscule. This is because the main thread will probably have a fresh time slice when it wakes from the blocked state while it waited for the ENTER key.

Note, this explanation assumes a single core. But even on a multicore system the thread_start thread is only going to be scheduled when another thread's time slice runs out. The chances of that happening while main's thread isn't holding the lock is small.

One possible test for my above hypothesis would be to call pthread_yield after releasing the lock. You'll probably want to do that in both threads. Even then I don't think it will GUARANTEE a thread switch every time.

torak
pthread_yield is what I was looking for.The program runs perfectly fine now.Thanks a lot.
Ashish
A: 

After reading the comments given by torak, I changed the code. Now it is working fine as expected.

[root@localhost threads]# diff -Nurp mutex.c mutex_modified.c 
--- mutex.c 2010-07-09 19:50:51.000000000 +0530
+++ mutex_modified.c    2010-07-09 19:50:35.000000000 +0530
@@ -18,6 +18,7 @@ pthread_mutex_lock(&work_mutex);
 printf("Enter some text\n");
 fgets(work_area, WORK_SIZE, stdin);
 pthread_mutex_unlock(&work_mutex);
+sleep(1);
 }
 return 0;
 }
@@ -30,5 +31,6 @@ while(1)
 pthread_mutex_lock(&work_mutex);
 printf("You enetered %d char",strlen(work_area));
 pthread_mutex_unlock(&work_mutex);
+sleep(1);
 }
 }

It is still very confusing though. Although it was a test program, what should one do to avoid such kind of situation while coding a real application?

Ashish
For a real application: use `pthread_yield` instead of `sleep(1)`.
Ken Bloom
A: 

Here's a slightly more awkward solution that's guaranteed to work: #include

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

#define WORK_SIZE 1024
pthread_mutex_t work_mutex;
char input_area[WORK_SIZE];
char work_area[WORK_SIZE];

void *thread_start(void *);

int main()
{
    pthread_t a_thread;
    pthread_mutex_init(&work_mutex,NULL);
    work_area[0] = 0;

    pthread_create(&a_thread,NULL,thread_start,NULL);
    while(1) {
        printf("Enter some text\n");
        fgets(input_area, WORK_SIZE, stdin);
        pthread_mutex_lock(&work_mutex);
        while (work_area[0] != 0) {
            pthread_mutex_unlock(&work_mutex);
            sleep(1);
            pthread_mutex_lock(&work_mutex);
        }
        memcpy(work_area, input_area, WORK_SIZE);
        pthread_mutex_unlock(&work_mutex);
    }
    return 0;
}

void *thread_start(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&work_mutex);
        if (work_area[0] > 0) {
            printf("you enetered %d char\n",strlen(work_area));
            work_area[0] = 0;
            pthread_mutex_unlock(&work_mutex);
        } else {
            pthread_mutex_unlock(&work_mutex);
            sleep(1);
        }
    }
}

Note that because POSIX mutex locks are thread-specific, the consumer thread can't wait on a signal from the producer thread, so here it just wakes up every second to check for new data. Similarly, if the producer needs to put something in queue and the work area is full, it waits by seconds until the consumer gets around to emptying the buffer. You could get rid of some delays by using pthread_yield instead of sleep, but then your threads will 'busy-wait', consuming lots of CPU to check again and again for their condition to be met

Note that if you want it to print a line for 0-character entries you could add a separate bool indicating whether there's new data in the queue.

Walter Mundt