0

I'm having a problem deciding on what to do in this situation, I want to have a detached thread, but still be able to join it in case I want to abort it early, presumably before starting a new instance of it, to make sure I don't have the thread still accessing things when it shouldn't.

This means I shouldn't detach the thread right after calling it, so then I have a few options:

  • Self-detach the thread when it's reaching the end of its execution, but then wouldn't this cause problems if I try to join it from the main thread? This would be my prefered solution if the problem of trying to join it after it's self-detached could be solved. I could dereference the thread handle that the main thread has access to from the self-detaching thread before self-detaching it, however in case the main thread tries to join right before the handle is dereferenced and the thread self-detached this could cause problems, so I'd have to protect the dereferencing in the thread and however (I don't know how, I might need to create a variable to indicate this) I would check if I should join in the main thread with a mutex, which complicates things. Somehow I have a feeling that this isn't the right way to do it.
  • Leave the thread hanging until eventually I join it, which could take a long time to happen, depending on how I organise things it could be not before I get rid of what it made (e.g. joining the thread right before freeing an image that was loaded/processed by the thread when I don't need it anymore)
  • Have the main thread poll periodically to know when the thread has done its job, then join it (or detach it actually) and indicate not to try joining it again?
  • Or should I just call pthread_exit() from the thread, but then what if I try to join it?

If I sound a bit confused it's because I am. I'm writing in C99 using TinyCThread, a simple wrapper to pthread and Win32 API threading. I'm not even sure how to dereference my thread handles, on Windows the thread handle is HANDLE, and setting a handle to NULL seems to do it, I'm not sure that's the right way to do it with the pthread_t type.

Epilogue: Based on John Bollinger's answer I chose to go with detaching the thread, putting most of that thread's code in a mutex, this way if any other thread wants to block until the thread is practically done it can use that mutex.

Michel Rouzic
  • 1,013
  • 1
  • 9
  • 22

2 Answers2

1

The price of using an abstraction layer such as TinyCThreads is that you can rely only on the defined characteristics of the abstraction. Both Windows and POSIX provide features and details that are not necessarily reflected by TinyCThreads. On the other hand, this may force you to rely on a firmer foundation than you might otherwise hack together with the help of implementation-specific features.

Anyway, you say,

I want to have a detached thread, but still be able to join it in case I want to abort it early,

but that's inconsistent. Once you detach a thread, you cannot join it. I suspect you meant something more like, "I want a thread that I can join as long as it is running, but that I don't have to join when it terminates." That's at least consistent, but it focuses on mechanism.

What I think you actually want would be described better as a thread that you can cancel synchronously as long as it is running, but that you otherwise don't need to join when it terminates. I note, however, that the whole idea presupposes a way to make the thread terminate early, and it does not appear that TinyCThread provides any built-in facility for that. It will also require a mechanism to determine whether a given thread is still alive, and TinyCThread does not provide that, either.

First, then, you need some additional per-thread shared state that tracks thread status (running / abort requested / terminated). Because the state is shared, you'll need a mutex to protect it, and that will probably need to be per-thread, too. Furthermore, in order to enable one thread (e.g. the main one) to wait for that state to change when it cancels a thread, it will need a per-thread condition variable.

With that in place, the new thread can self-detach, but it must periodically check whether an abort has been requested. When the thread ends its work, whether because it discovers an abort has been requested or because it reaches the normal end of its work, it performs any needed cleanup, sets the status to "terminated", broadcasts to the CV, and exits.

Any thread that wants to cancel another locks the associated mutex, and checks whether the thread is already terminated. If not, it sets the thread status to "abort requested", and waits on the condition variable until the status becomes "terminated". If desired, you could use a timed wait to allow the cancellation request to time out. After successfully canceling the thread, it may be possible to clean up the mutex, cv, and shared variable.

I note that all of that hinges on my interpretation of your request, and in particular, on the prospect that what you're after is aborting / canceling threads. None of the alternatives you floated seem to address that; for the most part they abandon the unwanted thread, which does not serve your expressed interest in preventing it from making unwanted changes to shared state.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • You're right. So I do want to be able to cancel a thread and wait for it to be over before proceeding, using a mutex is a good idea. The way I currently typically do it is that I pass a pointer to a structure to my thread which contains among other things a `volatile int abort` element which tells the thread to stop its loops. Since the main function only writes it and the thread only reads and both operations are effectively atomic (we're changing just one bit here) I don't need a mutex for that. You're also right that I don't care for the return value. – Michel Rouzic Oct 23 '17 at 15:10
  • You are mistaken, @MichelRouzic. A `volatile` variable does not have the semantics you need. You could use a variable with *atomic* type, but I'm not sure whether MSVC supports that, at least with C11 syntax. In any event, you *do* need a CV, and therefore you need a mutex, so the whole question of `volatile` *vs*. atomic *vs*. vanilla is moot. – John Bollinger Oct 23 '17 at 15:15
  • So I should detach the thread from the start, put most of its code in a mutex, then in the main thread I should set my abort variable then try to lock the mutex (then unlock it right away), which would block the main thread until the thread is pretty much done, correct? It seems like a pretty elegant solution actually, thank you! I guess actually I could not block the main thread and start a second instance of the thread, the mutex would block the second instance until the first unlocks its mutex, which could be even better. – Michel Rouzic Oct 23 '17 at 15:15
  • I don't see why my using a volatile int wouldn't do it. I used a non-volatile int before, but due to optimisation the thread wouldn't get the message. I don't believe I need an atomic type because what could go wrong? Either you read a value of 0 or 1, there's no way to screw that up, even if the reading and writing are concurrent and even if the variable isn't properly aligned or in a single cache line. Wait, what's a "CV"? – Michel Rouzic Oct 23 '17 at 15:19
  • If you want to avoid a race condition then you need to use a CV (a semaphore could do it, too, but TinyCThreads does not appear to offer one, and you'd need a CV to implement your own). If you don't care about asking the target thread to abort early -- that is, if you're satisfied to let it run to completion, even though you no longer care about the computation it's performing -- then you can structure it more simply than I described in my answer. – John Bollinger Oct 23 '17 at 15:20
  • @MichelRouzic, as for `volatile`, see the discussions [here](https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming) and [here](https://stackoverflow.com/questions/2484980/why-is-volatile-not-considered-useful-in-multithreaded-c-or-c-programming). You could easily find more. – John Bollinger Oct 23 '17 at 15:22
  • What's a CV? I don't know what that stands for. And I don't absolutely need the first instance of the thread to exit before starting the second instance, I just need to make sure it's done reading or writing from/to the data before the second instance of the thread does it too, so the mutex approach is a very good idea, I should mark your answer as accepted I suppose. And considering what I need with that 0 or 1 variable I really don't think I need atomicity, because again, what could possibly go wrong in this type of case? – Michel Rouzic Oct 23 '17 at 15:25
  • 1
    @MichelRouzic, CV = condition variable. TinyCThreads appears to provide an implementation. – John Bollinger Oct 23 '17 at 15:27
  • As for what could go wrong if you use an unprotected `volatile` variable to share data between threads, where at least one modifies it, the answer is literally ***anything***. Such a situation constitutes a data race, which makes the behavior undefined. This is explicitly covered by the C standard. – John Bollinger Oct 23 '17 at 15:32
  • It sounds like you may be thinking in terms of Java semantics for `volatile`. C is not Java, and in C, `volatile` does not have the same semantics that it does in Java. – John Bollinger Oct 23 '17 at 15:34
  • Tell me concretely what could go wrong in this very narrow situation: The variable is set to 0, then the thread is started. The thread reads the variable over and over again, only caring when the variable isn't 0 anymore. Back in the main thread, at some point it BLINDLY changes ONE BIT of that variable, just once. And then that's it, it's all that matters. Nothing could possibly go wrong in that very particular very specific case. And I know what `volatile` does, it tells the compiler to keep using the actual value, nothing else, which is all I need from it. I need to look into CVs though – Michel Rouzic Oct 23 '17 at 16:22
  • The important point is that I can spare the usage of a mutex for this particular situation, and when you can spare a mutex you should. And there's not a single implementation that could possibly screw this up, because the memory for that variable is effectively always at all times either 0 or 1, no matter what. It wouldn't be the case if I wrote -1, it might come out temporarily as -65536. There's no incrementation, no reading and then writing, only one thread writes (once!), and only one thread reads, and only one bit even matters! – Michel Rouzic Oct 23 '17 at 16:28
  • 1
    @MichelRouzic, in practice, on modern hardware and implementations, the low-level issue is that accesses to your volatile variable can be reordered relative to the code around them, both by the compiler and by the CPU. Although that would not affect the observable behavior of any single thread, it is entirely capable of affecting inter-thread actions. For example, to other threads, it might appear that a thread turns on its "terminated" flag before it actually finishes. – John Bollinger Oct 23 '17 at 17:18
  • Sure, but in this situation it wouldn't be a problem, since the only thing that matters is that the thread finds out that it has to stop. In a more general way of course you're absolutely right, but in this very particular I can absolutely get away with this. – Michel Rouzic Oct 23 '17 at 17:40
0

It's not clear to me what you want, but you can use a condition variable to implement basically arbitrary joining semantics for threads. The POSIX Rationale contains an example of this, showing how to implement pthread_join with a timeout (search for timed_thread).

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92