Understanding pthreads

Started by
10 comments, last by the_edd 13 years, 6 months ago
I have a couple of questions involving pthreads. In the following code below, the three threads are active but only one of them is running on the cpu, is this correct? If so is a mutex needed because of context switching, because if one thread is running at a time on the cpu only that thread can access the global variable param.

Also when sched_yield is called the thread that called it gets put back in the queue, but when it gets its turn to run on the cpu again does it start executing the line after the sched_yield call?

int param;int main(int argc, char *argv[]){    pthread_t thread1, thread2, thread3;    int t_return1, t_return2, t_return3;        param = 1;        t_return1 = pthread_create(&thread1,NULL,Function,NULL);    t_return2 = pthread_create(&thread2,NULL,Function,NULL);    t_return3 = pthread_create(&thread3,NULL,Function,NULL);        pthread_join(thread1,NULL);    pthread_join(thread2,NULL);    pthread_join(thread3,NULL);            cin.get();    return 0;}void *Function(void *ptr){    while(param < p_argc)    {                        //...cut code..\\        if(i == str_len-1)        {            param++;        }        sched_yield();            }    pthread_exit(NULL);}
Advertisement
Quote:
the three threads are active but only one of them is running on the cpu, is this correct?

No.

Once you launch the threads, they will spread out and run on any available cpu threads. (core != threads, some technology like hyper-threading results in 2 or more hardware threads per cpu core) Your threads are fairly small jobs, and so it might act sequential, but that isn't something you can count on. Consider your situation as if all the threads are running at once.

Quote:
If so is a mutex needed because of context switching, because if one thread is running at a time on the cpu only that thread can access the global variable param.

Yes you need the mutex.

Most operations aren't atomic. A thread can get interupted in the middle of "param++;", and then param is garbage.
++param is often seen in assembly as load param, increment param, store param.
param++ could be more instructions because it creates a temporary as well.
One thread can load and get interupted. The other thread could then load, increment, and store. The first thread now has the wrong value for param.

Also, the compiler has no idea param is shared between threads since C++ doesn't have a notion of threading. So it is possible that param could get optimized in a way that breaks your threading (ie it isn't saved/loaded between thread contexts). Putting in locks also adds memory barriers that help to insure that items are properly saved out to ram between contexts. Though you may still have to mark it volitile to help keep the compiler from optimizing out the load/save entierly.

Quote:
Also when sched_yield...

Yes, the thread picks up right after the yield when the OS gives it more time.

[Edited by - KulSeran on October 16, 2010 11:07:45 PM]
Is there a way to avoid using a mutex if it is just one global variable being accessed by multiple threads?
Depending on what you are doing... maybe.

If you only have readers, then you don't need a mutex.

There are a whole class of OS and processor specific functions like InterlockedCompareExchange that you can use to manipulate single variables in a thread safe manner. You'll have to look up the proper instrinsics for your compiler and OS, or look up the assembly instructions for your specific processor. This is probably more work than it is worth in most cases. And it can still be tricky to get the behavior you want, as hand rolling your own lock-free structures is hard.

What exactly are you doing that you are afraid of a lock? There are really only two cases you have to worry about. Case one is that you actually just need the lock. Case two is that by locking you are making your parallel computation effectively serial, and you need to rethink your strategy.

Your sample code above makes it look like you are iterating over an array of program options (and hopefully doing a lot of work per item). You may just want to pre-partition your parameters before starting the threads. Then they don't need to share a global 'param' variable. Each thread could know from the start what parameters it will handle. ie. Thread1 handles [0,10), thread2 handles [11,20), and thread3 handles [21,30).
The program I am writing is for a class Im taking. Yes the threads iterate through the program options and do some processing with them. But primitive mutexes and semaphores can't be used which has me stumped.
Quote:Original post by TheBinaryMan
The program I am writing is for a class Im taking. Yes the threads iterate through the program options and do some processing with them. But primitive mutexes and semaphores can't be used which has me stumped.


So the assignment itself bans you from using them?

If so, you'll have to treat all of your input as immutable and make a copy every time you want to work with the data on another thread.

EDIT: and are you in fact sure that the assignment requires shared state in the first place? I mean if there's one output for every input, you might not need to worry about synchronization.
Quote:So the assignment itself bans you from using them?

Yes the assignment itself bans using them.

The instructor recommended use of global variables, so I wrote the program using them. Not only does each thread have to have access to the argc and argv parameters, (or global copies or references to them, ) but they must be able to update the counter that iterates through the contents of argv. The program works correctly with and without a mutex, but Im not sure if leaving it non thread safe would be wise. Im guessing it works without a mutex because the critical section is just a small update on a global variable, but by my understanding it can still mess up on a random run.
The fourth parameter of pthread_create() is for this exact type of thing.
Create a structure and send in the information beforehand. That way you can pre-partition the data into sets for each thread to work on. That way none of them work on overlapping segments of data, and none of them have to know what the others are working on. No global variables involved.
struct ThreadData{  int argc_start;  int argc_end;  char **argv;};ThreadData data1 = { 0, argc/3, argv };ThreadData data2 = { argc/3, argc*2/3, argv };ThreadData date3 = { argc*2/3, argc, argv };pthread_create( &thread1, NULL, Function, &data1 );...void *Function( void *ptr ){  ThreadData *data = (ThreadData*)ptr;  for( int arg = data->argc_start; arg < data->argc_end; ++arg )  {    //do stuff  }}


Quote:
The instructor recommended use of global variables, so I wrote the program using them.

Global state and threads don't mix. Your instructor is giving very bad advice there. First off, global state has to be properly protected from writers concurrent to readers (lots of locks). Secondly, because of all those locks, global state can become a huge point of contention that slows down your application to the point that the threads aren't gaining you anything. That said, a global store of information that all threads can only read from is usually a good thing. So, in this case, the char **argv could be put at a global level assuming your threads only need to read from that pointer.


Quote:
The program works correctly with and without a mutex, but Im not sure if leaving it non thread safe would be wise.

No, it isn't wise to leave out the mutex the way you had it written. You are just getting lucky because of the scope of your application. It can be really hard to track down threading bugs because of this exact type of thing. The issue may not occur all the time, or maybe even not at all on your test machine. But the bug is still there.
Yes passing a struct into each thread would solve that problem, but then another problem arises. My threads have to print the program options in the same order that they are received and each thread only prints certain ones. Since I can't control when each thread runs Im sure they will print out of order.
Either your instructor has given you an assignment which is meant to illustrate the value of synchronisation primitives by forbidding their use, or we're missing some information.

EDIT: just to be sure: the printing has to be done by the threads you create manually? You can't for example have your threads store the results of their work somewhere and have the main thread print those results once the threads have been pthread_join()ed?

This topic is closed to new replies.

Advertisement