3

I have the following code to try (and fail) to protect a critical region in my code. Basically I want a print statement to be fully executed by any thread before some other thread prints another statement.

In main.h:

pthread_mutex_t printer_mutex;

In main.c - the first thing the program does:

pthread_mutex_init(&printer_mutex, NULL);

The threads are created like so (I've removed some of the checks though):

for(long t = 0; t < NUM_THREADS; t++) {
   args[t].fw = fw;
   args[t].deriv_init = deriv_init;
   args[t].to_prove = fw.to_prove.at(i);
   pthread_create(&threads[t], NULL, do_derivation_helper, (void *) &args[t]);
}
for(long t = 0; t < NUM_THREADS; t++) {
  pthread_join(threads[t], NULL);
}

Then in a class of mine that does all the printing in the program I have the following. The printing should be locked and then unlocked when it is finished.

void InfoViewer::rule_x_fires() {
    // START CRITICAL REGION
    pthread_mutex_lock(&printer_mutex);
    cout << "INFO: Rule x fires" << endl;
    pthread_mutex_unlock(&printer_mutex);
    // END CRITICAL REGION
}

The undesired output I get is:

INFO: Rule x firesINFO: Rule x fires

I.e. the line is not ended before some other thread starts printing.

Any ideas? Am I not initialising correctly? Am I ok using standard C style threads in my C++ program?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
ale
  • 11,636
  • 27
  • 92
  • 149
  • What OS & compiler do you use? – Olympian Jul 20 '11 at 14:59
  • The *only* bad output you get is the omission of the newline? The printed lines are never split anywhere else? Do you get two messages followed by two newlines or only one newline? – Greg Howell Jul 20 '11 at 15:02
  • $ g++ -v Using built-in specs. Target: i686-apple-darwin10 Configured with: /var/tmp/gcc/gcc-5666.3~6/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1 Thread model: posix gcc version 4.2.1 (Apple Inc. build 5666) (dot 3) – ale Jul 20 '11 at 15:06
  • `.c` is not usually used as an extension to C++ programs. – Lightness Races in Orbit Jul 20 '11 at 15:16
  • @Tomalak Geret'kal: I did wonder that I was being lazy. I have used pthreads in C before so just used them in my current C++ project. What would be the best thing to do? – ale Jul 20 '11 at 15:19

3 Answers3

8

I believe the problem is in defining the mutex variable in the header file. That way every compilation unit gets its own version of such variable, and you just lock different mutexes. Simply do this:

// .h header file: declare
extern pthread_mutex_t printer_mutex;

// one of the .c/.cpp files: define
pthread_mutex_t printer_mutex;

Edit 0:

To explain why it "worked" with multiple definitions - PThread mutex could be initialized two different ways - dynamically with pthread_mutex_init, and statically with PTHREAD_MUTEX_INITIALIZER. Looking into pthread.h one can find the following (32-bit version):

# define PTHREAD_MUTEX_INITIALIZER \
    { { 0, 0, 0, 0, 0, { 0 } } }

This means that any static variable of type pthread_mutex_t will be correctly initialized without any explicit value assigned, or init call due to the fact that static memory is zero-filled. So you explicitly dynamically initialized one mutex, all others were implicitly initialized to all zeros.

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • Let me test this a few hundred times and get back to you.. seems good so far! – ale Jul 20 '11 at 15:14
  • 1
    +1 In C & C++ the rule is 'Headers only include declarations, definitions go in .c/.cpp files' – Greg Howell Jul 20 '11 at 15:14
  • 1
    This looks extremely suspicious. On the other hand if every compilation unit was getting its own version then there should have been in linker errors about violating the One Definition Rule. – David Hammen Jul 20 '11 at 15:15
  • The code from the same translation unit cannot access different mutexes. It could be trying to lock an uninitialized mutex. More to it, linker ought to be complaining about duplicate definition. – unkulunkulu Jul 20 '11 at 15:15
  • Oh, I GET it! OP uses main.c, the extension helps avoid the link-time error 'cause of mangling. – unkulunkulu Jul 20 '11 at 15:21
  • To Edit 0, it's ok, but why would one function in one translation unit lock different mutexes? Templates is another guess :) – unkulunkulu Jul 20 '11 at 15:23
  • but function InfoViewer::rule_x_fires() is defined in just one translation unit, isn't it? – unkulunkulu Jul 20 '11 at 15:34
  • @unkulunkulu The mutex is initialized in main, perhaps main is in a separate translation unit? – Greg Howell Jul 20 '11 at 15:38
  • Might've been inlined in multiple places? – Nikolai Fetissov Jul 20 '11 at 15:40
  • @unkulunkulu To extend my now uneditable previous comment: main() initializes the (unmangled) mutex in main.c's translation unit. If InfoViewer is implemented in a separate translation unit it will work with a different (name mangled) mutex, which is never initialized. – Greg Howell Jul 20 '11 at 15:45
  • @Greg the initialization question is covered in the answer we're discussing. – unkulunkulu Jul 20 '11 at 16:08
4

You can always change that use of std::cout to printf(). You are on a Mac, a POSIX-compliant OS. printf() is guaranteed to be atomic. In fact, you shouldn't even need a mutex if you can use printf such that all of the output to be produced at one time is performed by a single printf() call. There are no guarantees with std::cout. None.

If you do convert this particular use of std::cout to printf you should do that throughout your application. Mixing std::cout and printf is not the best idea.

David Hammen
  • 32,454
  • 9
  • 60
  • 108
  • David, can you point me to POSIX documentation where it describes `printf` to be atomic? I'd be interested to have that in my bookmarks. Thanks. – Nikolai Fetissov Jul 20 '11 at 17:46
  • @Nikolai: POSIX requires the existence and behavior of several functions, several of which are also defined in the C standard. The `printf` family are amongst those functions. The complete list of functions defined by POSIX is near the bottom of this page: http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_02.html . A small subset are allowed not to be thread-safe. The printf family is not a part of this subset; printf must be thread-safe. The list of exceptions is on this page: http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html (see section 2.9.1). – David Hammen Jul 20 '11 at 18:14
  • @Nikolai: Later on in http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html the article talks about 'cancelation points'. Each member of the printf family is listed. The standard is quite specific about what a compliant implementation has to do with regard to those cancelation points. – David Hammen Jul 20 '11 at 18:22
0

That's an interesting problem. I don't think this addresses the root problem (synchronization in ostream), but try this:

void InfoViewer::rule_x_fires() {
    // START CRITICAL REGION
    pthread_mutex_lock(&printer_mutex);
    cout << "INFO: Rule x fires" << endl;
    cout.flush();
    pthread_mutex_unlock(&printer_mutex);
    // END CRITICAL REGION
}
Klox
  • 931
  • 12
  • 21
  • This was my initial thought too, but `std::endl` calls `flush()` after inserting the newline character. – Praetorian Jul 20 '11 at 15:05
  • It's *supposed* to call flush but it's still worth trying. I'd try just the endl, endl + flush and \n + flush. The fact that the output interleaves *only* around the newline is very strange, and suggests the problem may not be a synchronization issue. – Greg Howell Jul 20 '11 at 15:08
  • flush() doesn't fix it sorry :-S – ale Jul 20 '11 at 15:09