-2

I'm working in a mingw program with two threads (pthread_t) in addition to the main execution. Each thread contains an endless loop for continuous communication with an external instrument, each communication having different speed and protocol. Main program is just displaying instrument data at run time, upon user request. The endless loops in the threads will only exit when a "keepalive" variable is set to 0 in main. To exchange data between threads, I would like to know the pros and cons of the two ways that I know:

  • passing arguments to the thread (a structure can be passed using the last argument of pthread_create: *arg).

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                      void *(*start_routine) (void *), void *arg);
    
  • using global variables and controlling the concurrency (pthread_mutex_lock and pthread_mutex_unlock)

Actually I'm using both ways (mainly because I needed an early "working program version"). As an example of the variable exchanges I'm refering to:

  1. I'm defining the "keepalive" variable as global (to share it when the threads enter the endless loop).
  2. I'm passing the communication configuration data by means of the struct.
  3. I'm retrieving the instrument data to main by means of a global array (updated within the endless loops)
  4. I'm retrieving the handles to files and communication with the struct (at the end, to close and free everything properly).

I would appreciate some advice on drawbacks about using global variables versus using the struct with atomic variables. (Code cleanliness, speed, efficiency, cpu/memory usage...).

Thanks.

  • 3
    Avoid global variables, and that's a general tip that's equally valid if you're using threads or not. – Some programmer dude May 16 '16 at 05:55
  • You definitely should use atomic variables. – Linus May 16 '16 at 05:57
  • I see a close vote due to "opinion based". Considering the current wording of the question, this might be true. Changing it to ask for possible drawbacks of the use of global variables for thread synchronization could mitigate that. Then again, having answered the question, it occurred to me that this question probably would've been a better fit for programmers.stackexchange.com, or? – Daniel Jour May 16 '16 at 06:27

2 Answers2

4

It's almost universally true that you shouldn't use global variables. (Also I think you possibly didn't understood what extern is for.)

Then ...

[..] because I'm concerned about speed and efficiency.

You're utilizing locking to synchronize access. Thus, forget about it, the overhead induced due to that will shadow anything else. Using a global potentially could be "faster" due to a (link time) known address. I seriously doubt that this will be of any relevance, though. Don't optimize prematurely without consulting a profiler.

A drawback of a global variable is that it gets harder to test the communication between the threads. When passing a struct, you can easily start another thread, and let that thread communicate with the end points whose communication you want to test. You don't need to change either endpoint for that, just the way these threads are started.

Also, due to similar reasons, it's not as easy to scale your application. Suppose you have two threads, for example an "searcher" (used to search through some data) and an "indexer" (responsible to store matches, and possibly detach further actions). If they communicate through a global variable, how would you add another searcher thread? You'd have to use another global for the communication between that new searcher and the indexer, duplicating most of the searcher thread (all that changed is the variable used for communication). Compare with passing a struct containing "the communication channel": Here, you don't even need to touch the searcher code, just start another one. (The indexer must be changed to be able to have multiple sources of input in both examples, though).

Finally, passing the "communication channel" via a struct on thread start makes it possible to hide the communication channel, thus restricting which threads have access to it. This can be extremely valuable when trying to figure out who wrote / read from the channel. ("Which thread was it that wrote that sh#t?")

Community
  • 1
  • 1
Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
0

If you can, try to avoid using global variables because they might lead to concurrency issues and makes your code so much less readable. I suggest creating a userdata struct containing atomic type variables. Something like this:

struct user_data {
  atomic_bool myBool;
  atomic_int myInt;
};

int main() {
  pthread_t myThread;

  struct user_data userdata = {
    .myBool = &ATOMIC_VAR_INIT(false),
    .myInt = &ATOMIC_VAR_INIT(0)
  };
  if (pthread_create(&myThread, NULL, myFunction, &userdata) {
    // error handling
  }
}

This will avoid concurrency issues and make your code much more readable. To modify atomic variables use the atomic operations such as:

atomic_store
atomic_load
atomic_fetch_add

Here is a reference to the atomic operations library.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Linus
  • 1,516
  • 17
  • 35