Is there an invalid pthread_t id?
Asked Answered
P

8

59

I would like to call pthread_join for a given thread id, but only if that thread has been started. The safe solution might be to add a variable to track which thread where started or not. However, I wonder if checking pthread_t variables is possible, something like the following code.

pthread_t thr1 = some_invalid_value; //0 ?
pthread_t thr2 = some_invalid_value;

/* thread 1 and 2 are strated or not depending on various condition */
....

/* cleanup */
if(thr1 != some_invalid_value)
    pthread_join(&thr1);

if(thr2 != some_invalid_value)
    pthread_join(&thr2);

Where some_invalid_value could be 0, or an implementation dependant 'PTHREAD_INVALID_ID' macro

PS : My assumption is that pthread_t types are comparable and assignable, assumption based on

PPS : I wanted to do this, because I thought calling pthread_join on invalid thread id was undefinde behaviour. It is not. However, joining a previously joined thread IS undefined behaviour. Now let's assume the above "function" is called repeatedly. Unconditionnally calling pthread_join and checking the result might result in calling pthread_join on a previously joined thread.

Pereira answered 8/6, 2011 at 9:49 Comment(2)
You could always use pthread_self()... one thread you presumably won't be joining ;-). Can use pthread_t invalid_thread = pthread_self() to aid readability.Hulbig
For C++, std::optional<pthread_t> is another possible option.Hanhhank
B
19

Your assumption is incorrect to start with. pthread_t objects are opaque. You cannot compare pthread_t types directly in C. You should use pthread_equal instead.

Another consideration is that if pthread_create fails, the contents of your pthread_t will be undefined. It may not be set to your invalid value any more.

My preference is to keep the return values of the pthread_create calls (along with the thread IDs) and use that to determine whether each thread was started correctly.

Bearskin answered 8/6, 2011 at 10:8 Comment(2)
You can compare pthread_t types directly in C. sys/types.h states all types are defined as arithmetic types (with some exceptions that exclude pthread_t). I think you meant that you shouldn't compare pthread_t typesDisjointed
@OLL: pthread is not always an arithmetic type. It's true for unix/Linux, but may be false for other OS', such as Windows (Win32)Antinucleon
A
17

As suggested by Tony, you can use pthread_self() in this situation.

But do not compare thread_ts using == or !=. Use pthread_equal.

From the pthread_self man page:

Therefore, variables of type pthread_t can't portably be compared using the C equality operator (==); use pthread_equal(3) instead.

Agulhas answered 8/6, 2011 at 10:4 Comment(1)
+1 - just to elaborate in light of qbert220's recommendation to use separate flags to track invalid threads, the usage here would be: if (pthread_create(&thr, ...) != 0) thr = pthread_self();.Hulbig
P
3

I recently ran into this same issue. If pthread_create() failed, I ended up with a undefined, invalid value stored in my phtread_t structure. As a result, I keep a boolean associated with each thread that gets set to true if pthread_create() succeeded.

Then all I need to do is:

void* status;
if (my_thread_running) {
  pthread_join(thread, &status);
  my_thread_running = false;
}
Polard answered 3/2, 2014 at 20:27 Comment(0)
O
2

Our issue was that we couldn't know if a pthread had been started or not, so we make it a pointer and allocate/de-allocate it and set it to NULL when not in use:

To start:

pthread_t *pth = NULL;

pth = malloc(sizeof(pthread_t));
int ret = pthread_create(pth, NULL, mythread, NULL);
if( ret != 0 )
{
    free(pth);
    pth = NULL;
}
    

And later when we need to join, whether or not the thread was started:

if (pth != NULL)
{
    pthread_join(*pth, NULL);
    free(pth);
    pth = NULL;
}

If you need to respawn threads quickly then the malloc/free cycle is undesirable, but it works for simple cases like ours.

Odle answered 12/10, 2021 at 19:4 Comment(3)
std::optional<pthread_t> would've been a better choice; for >= C++17 anyways. boost::optional<pthread_t> if you need <= C++14 also.Hanhhank
@JasonC, thanks for the note for others. In our case this was in C for the www.xnec2c.org project.Odle
Then std::optional<pthread_t> would've been a terrible choice. 😂Hanhhank
W
1

Unfortunately, on systems where pthread_t is a pointer, pthread_equal() can return equality even though the two args refer to different threads, e.g. a thread can exit and a new thread can be created with the same pthread_t pointer value.

Wiggly answered 3/7, 2013 at 1:3 Comment(1)
The direct comparison, of course, would fail too in this case. :-)Vidovik
F
1

I was porting some code that used pthreads into a C++ application, and I had the same question. I decided it was easier to switch to the C++ std::thread object, which has the .joinable() method to decide whether or not to join, i.e.

 if (t.joinable()) t.join();

I found that just calling pthead_join on a bad pthread_t value (as a result of pthread_create failing) caused a seg fault, not just an error return value.

Fregger answered 13/5, 2019 at 20:8 Comment(0)
K
0

This is an excellent question that I really wish would get more discussion in C++ classes and code tests.

One option for some systems-- which may seem like overkill to you, but has come in handy for me-- is to start a thread which does nothing other than efficiently wait for a tear-down signal, then quit. This thread stays running for the life of the application, going down very late in the shutdown sequence. Until that point, the ID of this thread can effectively be used as an "invalid thread" value-- or, more likely, as an "uninitialized" sentinel-- for most purposes. For example, my debug libraries typically track the threads from which mutexes were locked. This requires initialization of that tracking value to something sensible. Because POSIX rather stupidly declined to require that platforms define an INVALID_THREAD_ID, and because my libraries allow main() to lock things (making the pthread_self checks that are a good solution pthread_create unusable for lock tracking), this is the solution I have come to use. It works on any platform.

Note, however, that you have a little more design work to do if you want this to be able to initialize static thread references to invalid values.

Kasher answered 17/4, 2019 at 0:5 Comment(0)
H
0

For C++ (not really sure what language the OP was asking about) another simple option (but I think this one is still the simplest as long as you don't need to treat pthread_self() as valid anywhere) would be to use std::optional<pthread_t> (or boost's version if you can't swing C++17 or later, or implement something similar yourself).

Then use .has_value() to check if values are valid, and be happy:

// so for example:
std::optional<pthread_t> create_thread (...) {
    pthread_t thread;
    if (pthread_create(&thread, ...))
       return std::optional<pthread_t>();
    else
       return thread;
}

// then:

std::optional<pthread_t> id = create_thread(...);

if (id.has_value()) {
    pthread_join(id.value(), ...);
} else {
    ...;
}

You still need to use pthread_equal when comparing valid values, so all the same caveats apply. However, you can reliably compare any value to an invalid value, so stuff like this will be fine:

// the default constructed optional has no value
const std::optional<pthread_t> InvalidID;

pthread_t id1 = /* from somewhere, no concept of 'invalid'. */;
std::optional<pthread_t> id2 = /* from somewhere. */;

// all of these will still work:
if (id1 == InvalidID) { } // ok: always false
if (id1 != InvalidID) { } // ok: always true
if (id2 == InvalidID) { } // ok: true if invalid, false if not.
if (id2 != InvalidID) { } // ok: true if valud, false if not.

Btw, if you also want to give yourself some proper comparison operators, though, or if you want to make a drop-in replacement for a pthread_t (where you don't have to call .value()), you'll have to write your own little wrapper class to handle all the implicit conversions. It's pretty straightforward, but also getting off-topic, so I'll just drop some code here and give info in the comments if anybody asks. Here are 3 options depending on how old of a C++ you want to support. They provide implicit conversion to/from pthread_t (can't do pthread_t* though), so code changes should be minimal:

//-----------------------------------------------------------
// This first one is C++20 only:

#include <optional>

struct thread_id {
    thread_id () =default;
    thread_id (const pthread_t &t) : t_(t) { }
    operator pthread_t () const { return value(); }    
    friend bool operator == (const thread_id &L, const thread_id &R) {
        return (!L.valid() && !R.valid()) || (L.valid() && R.valid() && pthread_equal(L.value(), R.value()));
    }
    friend bool operator == (const pthread_t &L, const thread_id &R) { return thread_id(L) == R; }
    bool valid () const { return t_.has_value(); }
    void reset () { t_.reset(); }
    pthread_t value () const { return t_.value(); } // throws std::bad_optional_access if !valid()
private:
    std::optional<pthread_t> t_;
};


//-----------------------------------------------------------
// This works for C++17 and C++20. Adds a few more operator 
// overloads that aren't needed any more in C++20:

#include <optional>

struct thread_id {
    // construction / conversion
    thread_id () =default;
    thread_id (const pthread_t &t) : t_(t) { }
    operator pthread_t () const { return value(); }
    // comparisons
    friend bool operator == (const thread_id &L, const thread_id &R) {
        return (!L.valid() && !R.valid()) || (L.valid() && R.valid() && pthread_equal(L.value(), R.value()));
    }
    friend bool operator == (const thread_id &L, const pthread_t &R) {return L==thread_id(R);}
    friend bool operator == (const pthread_t &L, const thread_id &R) {return thread_id(L)==R;}
    friend bool operator != (const thread_id &L, const thread_id &R) {return !(L==R);}
    friend bool operator != (const thread_id &L, const pthread_t &R) {return L!=thread_id(R);}
    friend bool operator != (const pthread_t &L, const thread_id &R) {return thread_id(L)!=R;}
    // value access
    bool valid () const { return t_.has_value(); }
    void reset () { t_.reset(); }
    pthread_t value () const { return t_.value(); }  // throws std::bad_optional_access if !valid()
private:
    std::optional<pthread_t> t_;
};


//-----------------------------------------------------------
// This works for C++11, 14, 17, and 20. It replaces 
// std::optional with a flag and a custom exception.

struct bad_pthread_access : public std::runtime_error {
    bad_pthread_access () : std::runtime_error("value() called, but !valid()") { }
};

struct thread_id {
    thread_id () : valid_(false) { }
    thread_id (const pthread_t &t) : thr_(t), valid_(true) { }
    operator pthread_t () const { return value(); }
    friend bool operator == (const thread_id &L, const thread_id &R)  {
        return (!L.valid() && !R.valid()) || (L.valid() && R.valid() && pthread_equal(L.value(), R.value()));
    }
    friend bool operator == (const thread_id &L, const pthread_t &R) { return L==thread_id(R); }
    friend bool operator == (const pthread_t &L, const thread_id &R) { return thread_id(L)==R; }
    friend bool operator != (const thread_id &L, const thread_id &R) { return !(L==R); }
    friend bool operator != (const thread_id &L, const pthread_t &R) { return L!=thread_id(R); }
    friend bool operator != (const pthread_t &L, const thread_id &R) { return thread_id(L)!=R; }
    bool valid () const { return valid_; }
    void reset () { valid_ = false; }
    pthread_t value () const { // throws bad_pthread_access if !valid()
        if (!valid_) throw bad_pthread_access();
        return thr_; 
    }
private:
    pthread_t thr_;
    bool valid_;
};


//------------------------------------------------------
/* some random notes:

- `std::optional` doesn't let you specify custom comparison 
  functions, which would be convenient here.

- You can't write `bool operator == (pthread_t, pthread_t)`
  overloads, because they'll conflict with default operators
  on systems where `pthread_t` is a primitive type.

- You have to write overloads for all the combos of
  pthread_t/thread_id in <= C++17, otherwise resolution is 
  ambiguous with the implicit conversions.

- *Really* sloppy but thorough test: https://godbolt.org/z/GY639ovzd
Hanhhank answered 13/6, 2022 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.