0

I am trying to execute a function in a separate thread and I have noticed that sometimes the thread is executing twice.

If i also call pthread_join, the the code is executing only once every time I run the program.

I do not understand why this is happening.

I am attaching the code:

#include <stdio.h>
#include <pthread.h>
int func(){
  printf("Printing from a thread\n");
  return 0;
}
int main(void) {
  pthread_t ptid;
  pthread_create(&ptid,NULL,&func,NULL);
  //pthread_join(ptid,NULL);
  printf("Hello World\n");
  return 0;
}

EDIT: I have modified the code so the function has the correct header. I have attached the new code. The issue is still present.

#include <stdio.h>
#include <pthread.h>
void* func(void* arg){
  printf("Printing from a thread\n");
  return 0;
}
int main(void) {
  pthread_t ptid;
  pthread_create(&ptid,NULL,&func,NULL);
  //pthread_join(ptid,NULL);
  printf("Hello World\n");
  return 0;
}
yoyo_fun
  • 233
  • 1
  • 12
  • 4
    The function doesn't have the correct signature for a POSX pthread function. It should be `void *function(void *arg)`. You should be getting a compiler warning about a type mismatch. Theoretically, that means you've invoked undefined behaviour and anything can happen. – Jonathan Leffler Jun 04 '23 at 00:53
  • 2
    Possibly related: [When the main thread exits, do other threads also exit?](https://stackoverflow.com/q/11875956/12149471) – Andreas Wenzel Jun 04 '23 at 00:54
  • Which platform are you working on? If you don't call `pthread_join()`, your main thread may terminate before or after the secondary thread that runs `func()`. It isn't likely to cause problems, but the fact that it works as expected when you do call `pthread_join()` does suggest that may be a part of your problem. – Jonathan Leffler Jun 04 '23 at 00:55
  • @JonathanLeffler I am using an online compiler. I know that the code has a problem. However, I want to know what is happening behind the scenes with the compiler. I am exepcting that, when the pthread_join call is commented out, there are two possible results, either the thread is executed or not. I did not expect the thread to be executed twice. This has sparked my curiosity. – yoyo_fun Jun 04 '23 at 00:58
  • @yoyo_fun: Does the problem disappear if you make `func` the correct type, i.e. if you change the return type of `func` from `int` to `void *` and add a `void *` parameter? – Andreas Wenzel Jun 04 '23 at 01:05
  • @AndreasWenzel I have tested just now, and the problem still persists. – yoyo_fun Jun 04 '23 at 01:10
  • @yoyo_fun: Can you maybe edit the question to show your new code that still reproduces the problem. Normally, you should not change the question in such a way that it invalidates existing answers. However, since your question does not have any answers yet, this should not be a problem. It may also be useful if you post the exact output of your program instead of only describing it. – Andreas Wenzel Jun 04 '23 at 01:20
  • 4
    I am reasonably confident that the thread function is running only once. Presumably, you conclude that it runs twice on account of seeing its output twice, but I would be inclined to attribute that to some kind of weirdness around the shutdown sequence running concurrently with your thread's `printf()`. Your C implementation shouldn't produce such behavior, but then again, you shouldn't wantonly abandon your joinable threads without joining them. – John Bollinger Jun 04 '23 at 02:17
  • @yoyo_fun *I am using an online compiler* What online compiler? No C implementation should be flushing the same data twice from a `FILE` stream, assuming that's what's happening. – Andrew Henle Jun 04 '23 at 03:35
  • 2
    It might be informative to replace `printf("Printing from a thread\n");` with something like `static int counter = 0; printf("Printing from a thread, counter=%i\n", ++counter);` and see what output that yields. – Jeremy Friesner Jun 04 '23 at 05:13
  • Meanwhile, your question has an existing answer, so you can no longer overwrite your original code with the latest version of your code, because that would invalidate the answer. However, you can add your latest code to the bottom of your question and specify that you have the same problem with that code. – Andreas Wenzel Jun 04 '23 at 05:39
  • @JohnBollinger *I am reasonably confident that the thread function is running only once.* Yep - 'tis a GLIBC "feature". See the answer... – Andrew Henle Jun 04 '23 at 20:35
  • @AndreasWenzel I have edited the question and added the new code. – yoyo_fun Jun 06 '23 at 12:28
  • 1
    *The issue is still present* it will be until GLIBC fixes their buggy, broken implementation that does not conform with 7.21.2p7 of the C11 standard. – Andrew Henle Jun 06 '23 at 12:33

2 Answers2

4

I have noticed that sometimes the thread is executing twice.

You've observed a symptom and misattributed it to a particular ("runs twice") explanation. That explanation is incorrect.

I do not understand why this is happening.

It appears to be a bug in GLIBC. In particular, when one thread keeps calling printf, while another is calling exit, the output may appear twice, as the following program shows:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *p)
{
  size_t count = 0;
  while (1)
    printf("%zu\n", ++count);

  return NULL;
}

int main(void)
{
  pthread_t ptid;
  pthread_create(&ptid, NULL, func, NULL);
  usleep(1000);
  return 0;
}

This program should never print the same number twice, and yet running it produces:

1
2
3
...
290
291
292
293
294
294  <<<-- bug
295
296
296  <<<-- bug
297

The bug is "deliberate":

git show 64f01020838
commit 64f01020838a382d833254297f650d4a55933bde
Author: Ulrich Drepper <drepper@redhat.com>
Date:   Mon Feb 9 20:08:44 2004 +0000

    (_IO_cleanup): Do not use locking when flushing the buffers.  Some thread might be using a stream.

diff --git a/libio/genops.c b/libio/genops.c
index 58dac005d8..5b65e76bea 100644
--- a/libio/genops.c
+++ b/libio/genops.c
@@ -942,7 +942,9 @@ _IO_unbuffer_write ()
 int
 _IO_cleanup ()
 {
-  int result = INTUSE(_IO_flush_all) ();
+  /* We do *not* want locking.  Some threads might use streams but
+     that is there problem, we flush them underneath them.  */
+  int result = _IO_flush_all_lockp (0);

   /* We currently don't have a reliable mechanism for making sure that
      C++ static destructors are executed in the correct order.

That is, drepper@redhat.com believed that exit should just flush the stream regardless of what other threads may be doing and without locking. I have no idea what motivated that patch.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • 2
    *Your program has a data race (undefined behavior)* [The code posted in the question does not have a data race](https://port70.net/~nsz/c/c11/n1570.html#7.21.2p7): "Each stream has an associated lock that is used to prevent data races when multiple threads of execution access a stream" If there is a data race printing the output, the implementation is buggy. – Andrew Henle Jun 04 '23 at 11:17
  • 1
    @AndrewHenle I think you are right -- the "3 line" output implies that there is a GLIBC bug. I've updated the answer. – Employed Russian Jun 04 '23 at 19:19
  • 1
    Interesting. What I test your posted code using gcc 13.1 on godbolt using a smaller `usleep` interval, I am unable to reproduce the effect of getting duplicate lines, but I sometimes get the opposite effect: I get missing lines instead. For example, the output jumps from `872` to `942`. – Andreas Wenzel Jun 04 '23 at 19:36
  • "that is there problem" Ugh. And some people wonder why I'm down on the quality of Linux implementations. Crap like that is why I've reached the point I think the core functionality and reliability of Windows is better than Linux. Now throw in my latest experience with systemd's "logging" that takes three entire CPUs under load to lose tens of thousands of log entries at a time to do what straight logging to `rsyslogd` can do with 12% of one CPU without losing a single log entry. Nevermind the entire benighted insanity of an "OOM killer". :-o – Andrew Henle Jun 04 '23 at 20:28
  • At least do a `trylock()` and if you can't get the lock skip flushing the stream **and** skip dropping the buffering on that stream. That took me all of three minutes to figure out, and it's a solution that **doesn't** risk corrupting data, unlike the current GLIBC implementation that will flush a buffer out from under an active stream, then apparently drop the buffering, also possibly out from under an active stream. Hello, data corruption!!! JHMFC that's idiotic, unthinking garbage. – Andrew Henle Jun 04 '23 at 21:18
  • How grownups do it: https://github.com/illumos/illumos-gate/blob/master/usr/src/lib/libc/port/stdio/flush.c#L191 – Andrew Henle Jun 04 '23 at 22:02
  • @AndrewHenle "that is there problem" did they actually added a comment with a grammatical mistake? – yoyo_fun Jun 06 '23 at 13:31
  • @yoyo_fun: Yes, it should be "their problem", not "there". The person who wrote it uses German, not English, as their native language. – Andreas Wenzel Jun 06 '23 at 13:34
  • @yoyo_fun The grammatical error is meaningless. The "we flush them underneath them" is utterly brain-damaged. I'm currently nursing an 18-year-old cat in his last days. This cat has a brain tumor, is totally blind, can't find his food and needs to be carried to it multiple times a day, when excited any amount at all can only walk in a tight circle, and when he's not brain-tumor-induced crazy he's senescent. and has lost so much weight he weighs barely 2 kg. A blind, senile, brain-tumor-addled cat that can't locate a litter box ain't crazy enough to write that code. – Andrew Henle Jun 06 '23 at 14:02
0

The first mistake is that in the main function, when you use pthread_create, you are passing in a function with an int return value for the third argument when the return value should be a void pointer as stated on the man page for pthread_create. You should compile with warnings enabled to avoid this mistake in the future. Secondly, by commenting out pthread_join. You are telling the program that it does not need to wait for you thread to return a value or finish through pthread_exit. Therefore, your func function might not run for the duration of the program. Lastly, pthread_join causing the code to execute once is the correct action of the function as stated on the man page for the function. Here is sample code that further elaborates on what I mean:

#include <stdio.h>
#include <pthread.h>
void *code() // Declares the function correctly as a void pointer
{
    printf("Executing thread code\n");
    pthread_exit(NULL); // Correctly exits the code using pthread_exit
}
int main()
{
    pthread_t ptid;
    pthread_create(&ptid, NULL, &code, NULL); // Creates the thread and runs the code inside of it
    pthread_join(ptid, NULL);                 // Tells the program to wait for the thread to finish (if it has not already)
    printf("This will always print second.");
    return 0;
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
Rezno
  • 1
  • 3
  • 2
    The problem is not only the type of the return value, but also the missing `void *` parameter. See the man page to which you linked, for further information. – Andreas Wenzel Jun 04 '23 at 06:34