Is There an Invalid Pthread_T Id

Is there an invalid pthread_t id?

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.

On platforms where pthread_t is numeric, is 0 guaranteed to be an invalid value?

If I wanted a value that was either a pthread_t or invalid, I'd use a boost::optional<pthread_t> or std::optional<pthread_t> from C++1y.

This has very little overhead (bytes), expresses what I want (this value may or may not be a pthread_t), and doesn't rely on platform-specific behavior.

When does pthread_t have 0?

Try instead checking the return value against exactly zero. Only if return value is 0 did the thread creation succeed.

The errno manpage says 'Valid error numbers are all positive numbers'. For example, on Linux, the errno error numbers seem to be roughly betwen 0 and 200.

Also, print the message corresponding to the errno error number you get, using e.g. perror().

pthread_kill() with invalid thread

I think I've come to a realisation while driving home, and I suspect that many others may find this useful too...

It would appear that I've been treating the worker (the thread), and the task (what the thread is doing) as one and the same, when in fact, they are not.

As I've already established from the code snippets in the question, it is unreasonable to ask "does this thread exist" as the pthread_t is likely just a pointer (it certainly is on my target). It's almost certainly the wrong question.

The same goes for process IDs, file handles, malloc()'d memory, etc... they don't use unique and never repeating identifiers, and thus are not unique 'entities' that can be tested for their existence.

The suspicions that I raised in the question are likely true - I'm going to have to use something like an is_running flag for the task (not thread).

One approach that I've thought about is to use a semaphore initialized to one, sem_trywait(), sem_post() and pthread_cleanup_push(), as in the example below (cleanup missing for brevity).

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>

struct my_task {
sem_t can_start;
pthread_t tid;

/* task-related stuff */
};

void *my_task_worker(void *arg) {
struct my_task *task = arg;

pthread_cleanup_push(sem_post, &(task->can_start));

fprintf(stderr, "--- task starting!\n");
usleep(2500000);
fprintf(stderr, "--- task ending!\n");

pthread_cleanup_pop(1);

return NULL;
}

void my_task_start(struct my_task *task) {
int ret;

ret = sem_trywait(&(task->can_start));
if (ret != 0) {
if (errno != EAGAIN) {
perror("sem_trywait()");
exit(1);
}

fprintf(stderr, ">>> task already running...\n");
return;
}

ret = pthread_create(&(task->tid), NULL, my_task_worker, task);
if (ret != 0) {
perror("pthread_create()");
exit(1);
}

fprintf(stderr, ">>> started task!\n");

return;
}

int main(int argc, char *argv[]) {
int ret;
struct my_task task;
int i;

memset(&task, 0, sizeof(0));

ret = sem_init(&(task.can_start), 0, 1);
if (ret != 0)
{
perror("sem_init()");
return 1;
}

for (i = 0; i < 10; i++) {
my_task_start(&task);
sleep(1);
}

return 0;
}

Output:

>>> started task!
--- task starting!
>>> task already running...
>>> task already running...
--- task ending!
>>> started task!
--- task starting!
>>> task already running...
>>> task already running...
--- task ending!
>>> started task!
--- task starting!
>>> task already running...
>>> task already running...
--- task ending!
>>> started task!
--- task starting!

Why does pthread_join not take a thread pointer?


It takes a copy of the pthread_t.

Yes.

I just don't get why it doesn't take
a pointer to that already existing thread,

pthread_t is a thread identifier. The specs consistently refer to it that way. Copying it does not duplicate the thread itself, nor consume more memory than one pthread_t occupies.

rather than copying it and
passing it over; it seems like if anything, doing so would cause more
problems and more memory use.

It does not necessarily cause more memory use, as a pthread_t is not necessarily larger than a pointer. It might be a pointer, or an integer. Even if it is a structure, however, there is no reason to think that it so large that passing it by value presents a significant problem, because the specifics are under control of the pthreads implementation. Why would implementers shoot themselves in the foot that way? Note well that passing a structure by value is not inherently less efficient than passing a pointer.

As for problems other than excessive memory use, you would have to be more specific, but I don't see any issues inherent in accessing a copy of a thread identifier directly vs. accessing a common identifier object indirectly, for the purposes of those functions that accept a pthread_t by value.

Am I missing something?

I suspect that your concerns are tied up in a misunderstanding of type pthread_t as somehow carrying data supporting thread operation as opposed to simply identifying a thread.

You may also be supposing that pthreads is a library, with a particular implementation, whereas in fact, it is first and foremost a specification, designed to afford multiple implementations. This is part of the reason for defining abstract data type pthread_t instead of specifying int or struct something * -- implementations can choose what actual type to use.

Perhaps you are also focusing too closely on the API functions. Even if in some particular implementation, passing a pthread_t by value to, say, pthread_join() were less efficient than passing a pointer to one, how much of an impact do you suppose that would actually have? pthread_join() is called infrequently, and only in cases where the caller is prepared to block. What does it matter if argument passing consumes a few more nanoseconds than it might otherwise do?

I read the
manpage, it doesn't seem to offer a reason to this.

Few manual pages provide rationale for function design, but I think the most likely explanation is essentially that form follows function. Those functions that receive a pthread_t by value do so because they do not need or want to modify the caller's value. The functions' designs reflect that.



Related Topics



Leave a reply



Submit