是否存在无效的pthread_t id?

58

我想要针对一个给定的线程ID调用pthread_join,但仅在该线程已经启动时才进行。安全的解决方案可能是添加一个变量来跟踪哪个线程是否已经启动。不过,我想知道是否可能检查pthread_t变量,就像以下代码一样。

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);

其中,some_invalid_value 可能为0,也可能是一个实现相关的“PTHREAD_INVALID_ID”宏。

PS: 我的假设是 pthread_t 类型是可比较和可赋值的,基于

PPS: 我想这么做是因为我认为在无效线程id上调用 pthread_join 是未定义行为。事实并非如此。然而,在之前加入过的线程上进行再次加入是未定义的行为。现在让我们假设上述“函数”被重复调用。 无条件地调用 pthread_join 并检查结果可能会导致在之前加入过的线程上调用 pthread_join。


12
您可以始终使用pthread_self()......一个您预计不会加入的线程;-)。 可以使用pthread_t invalid_thread = pthread_self()来提高可读性。 - Tony Delroy
对于C++,std::optional<pthread_t>是另一个可能的选择。 - Jason C
8个回答

18
您的假设是不正确的。pthread_t 对象是不透明的。在C语言中,您无法直接比较 pthread_t 类型。您应该使用 pthread_equal 代替。
另一个需要考虑的问题是,如果 pthread_create 失败,则 pthread_t 的内容将未定义。它可能不再设置为您的无效值。
我更喜欢保留 pthread_create 调用的返回值(以及线程ID),并使用它来确定每个线程是否已正确启动。

1
在C语言中,您可以直接比较pthread_t类型。sys/types.h指定所有类型都定义为算术类型(除了排除pthread_t的某些例外情况)。我认为您的意思是不应该比较pthread_t类型。 - OLL
2
@OLL:pthread并不总是算术类型。这在Unix/Linux中是正确的,但在其他操作系统(如Windows(Win32))中可能是错误的。 - Robin Hsu

15

正如Tony建议的那样,在这种情况下,您可以使用pthread_self()

不要使用==!=比较thread_t。而是使用pthread_equal

pthread_self手册中可以得知:

因此,pthread_t类型的变量不能通过C等号运算符(==)进行可移植的比较;请改用pthread_equal(3)。


3
+1 - 为了进一步阐述qbert220的建议,使用单独的标志来跟踪无效线程,这里的用法是:if (pthread_create(&thr, ...) != 0) thr = pthread_self(); - Tony Delroy

3

我最近遇到了同样的问题。如果pthread_create()失败,我的phtread_t结构中就会存储一个未定义的、无效的值。因此,我为每个线程保留一个布尔值,如果pthread_create()成功,则将其设置为true。

然后我只需要执行以下操作:

void* status;
if (my_thread_running) {
  pthread_join(thread, &status);
  my_thread_running = false;
}

2
我们的问题在于无法确定pthread是否已经启动,所以我们将其变为指针,并在不使用时进行分配/释放,并将其设置为NULL:
开始:
pthread_t *pth = NULL;

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

后来当我们需要加入时,无论线程是否已经启动:

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

如果需要快速重生线程,那么malloc/free循环是不可取的,但对于像我们这样简单的情况,它是有效的。

std::optional<pthread_t> 在 C++17 及以上版本中是更好的选择;如果你需要在 C++14 或更早的版本中使用,则可以选择 boost::optional<pthread_t> - Jason C
1
@JasonC,感谢您为其他人提供的提示。在我们的情况下,这是针对www.xnec2c.org项目中的C语言。 - KJ7LNW
那么 std::optional<pthread_t> 就是一个糟糕的选择。 - Jason C

1

不幸的是,在 pthread_t 为指针的系统上,即使两个参数引用不同的线程, pthread_equal()也可能返回相等性,例如,一个线程可以退出并创建一个具有相同 pthread_t 指针值的新线程。


1
当然,在这种情况下直接比较也会失败。 :-) - FooF

1
我正在将使用pthread的代码移植到C++应用程序中,我有同样的疑问。我决定更换为C++的std::thread对象,该对象具有.joinable()方法来决定是否加入,即:
 if (t.joinable()) t.join();

我发现仅仅在一个错误的pthread_t值上调用pthead_join(由于pthread_create失败引起)会导致段错误,而不仅仅是返回错误值。


0

这是一个非常好的问题,我真的希望在C++课程和代码测试中能够得到更多的讨论。

对于某些系统来说,有一种选项可能会让你觉得过于复杂,但对我来说非常方便,那就是启动一个线程,仅仅有效地等待一个撤销信号,然后退出。这个线程将在应用程序的生命周期内保持运行,直到关闭序列非常晚的时候才会停止。在此之前,该线程的ID可以有效地用作“无效线程”值,或者更可能用作“未初始化”的标记,以满足大多数目的。例如,我的调试库通常跟踪从中锁定互斥量的线程。这需要将跟踪值初始化为某些合理的值。由于POSIX愚蠢地拒绝要求平台定义INVALID_THREAD_ID,并且因为我的库允许main()锁定东西(使得pthread_self检查成为一个好的解决方案,而pthread_create则无法用于锁跟踪),这就是我使用的解决方案。它适用于任何平台。

但是,请注意,如果您希望将静态线程引用初始化为无效值,则需要进行更多的设计工作。


0
对于C++(不确定原帖中在问什么语言),另一种简单的选择是使用std::optional<pthread_t>(如果您无法使用C++17或更高版本,则可以使用boost版本,或者自己实现类似的东西)。但我认为this one仍然是最简单的选择,只要您不需要将pthread_self()视为任何地方有效。
然后使用.has_value()检查值是否有效,并感到高兴:
// 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 {
    ...;
}

在比较有效值时,您仍然需要使用pthread_equal,因此所有相同的警告都适用。但是,您可以可靠地将任何值与无效值进行比较,因此像这样的内容将是可以接受的:

// 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.

顺便说一下,如果你想要给自己一些适当的比较运算符,或者如果你想要制作一个可插拔替换的 pthread_t(在这种情况下,你不必调用 .value()),那么你就需要编写自己的小包装类来处理所有的隐式转换。这很简单,但也有点跑题,所以我会在这里放一些代码,并在评论中提供信息,如果有人问的话。这里有 3 个选项,取决于你想支持多老的 C++。它们提供了对 pthread_t 的隐式转换(不能使用 pthread_t*),因此代码更改应该是最小的:
//-----------------------------------------------------------
// 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接