C - 创建 n 个线程

4

我正在研究下面的函数。这个函数应该创建n个线程。它还应该打印子线程的tid。但此时我有点困惑。当我执行它并且例如我创建了5个线程,它每次都返回相同的tid。据我所知,tid是调用者的线程ID。同一个调用者调用所有这些线程吗?还是我做错了什么。 以下是代码:

void spawnThreads( unsigned int n) {
   int threads = n, ret = -1;
   pthread_t * thread = malloc(sizeof(pthread_t)*threads);
   pid_t tid;
   int i;
   for(i = 0; i < threads; i++) {
       ret = pthread_creation(&thread[i], NULL, (void *(*)(void *)) foo, NULL); // foo does not do anything

       if( ret != 0) {
           printf("pthread error!\n");
       }

       tid = syscall(SYS_gettid);
       printf("%d %d\n", i, tid);
       printf("I just created thread %d\n", i);

       pthread_join(thread[i],NULL);
}

void * foo(void) {
    return NULL;
}

例如,当我输入 spawnThreads(4) 时,会得到以下输出:
 0 2411
 I just created thread 0

 1 2411
 I just created thread 1

 2 2411
 I just created thread 2

 3 2411
 I just created thread 3

总之,该函数应该打印>i< >tid<。其中>tid<表示子进程的TID,>i<从1到n运行。

但是为什么我会得到四倍相同的tid?我做错了什么?如果有人能解释一下发生了什么事情,我会非常感激。


这不是你实际的代码。请参考 pthread_creation - Erik
1
还有:(void *(*)(void *)) 这是什么鬼? - Kninnug
你仅在主线程中调用 syscall(SYS_gettid),而非你创建的新线程... - Dmitri
@nos 我知道,尽管我不得不读几遍。它只是有点像LISP。 - Kninnug
2个回答

3
你每个线程都获取相同的TID的原因是每次都从主线程调用syscall(SYS_gettid),而不是在新创建的线程中调用。你需要在线程函数内部调用它,然后提供一种方法将信息传递回主线程(如果需要的话)。以下是一种实现方式(省略了一些错误检查):
创建一个结构体来保存互斥锁、条件变量、TID以及指示TID何时有效的标志。
struct s_threadId {
  pthread_mutex_t   mtx;    /* mutex & condition to allow main thread to
                               wait for the new thread to  set its TID */
  pthread_cond_t    cond;   /* '' */
  pid_t             id;     /* to hold new thread's TID */
  int               ready;  /* to indicate when 'id' is valid, in case
                               the condition wait gets interrupted */
};

然后将您的线程函数更改为锁定、设置和发信号(并将其移动,以便在spawnThreads()之前可见声明):

void *foo(void *arg)
{
  struct s_threadId *thId = arg;

  /* Lock mutex... */
  pthread_mutex_lock(&thId->mtx);

  /* Get and save TID and ready flag.. */
  thId->id = syscall(SYS_gettid);
  thId->ready = 1;
  /* ..and signal main thread that we're ready */
  pthread_cond_signal(&thId->cond);

  /* ..then unlock when we're done. */
  pthread_mutex_unlock(&thId->mtx);

  /* ... */

  return NULL;
}

...并修改你的spawnThreads函数,以在线程设置之后初始化/清理结构成员并获取TID:

void spawnThreads(unsigned int n)
{
  pthread_t thread; /* reused for each thread, since they run 1 at a time */

  /* struct to pass back TID */
  struct s_threadId threadId;
  pthread_cond_init(&threadId.cond, NULL);  /* init condition */
  pthread_mutex_init(&threadId.mtx, NULL);  /* init mutex */

  int i;
  for (i = 0; i < n; i++) {
    /* lock mutex *before* creating the thread, to make the new thread
       wait until we're ready before signaling us */
    pthread_mutex_lock(&threadId.mtx);

    /* clear ready flag before creating each thread */
    threadId.ready = 0;
    /* create threads and pass address of struct as argument */
    if (pthread_create(&thread, NULL, foo, &threadId)) {
      printf("pthread error!\n");
    } else {
      /* Wait on the condition until the ready flag is set */
      while (!threadId.ready) {
        pthread_cond_wait(&threadId.cond, &threadId.mtx);
      }
      /* Now we have the TID... */
      printf("%d %d\n", i, threadId.id);
      printf("I just created thread %d\n", i);
    }
    /* ..and unlock the mutex when done. */
    pthread_mutex_unlock(&threadId.mtx);

    pthread_join(thread, NULL);
  }

  /* When we're completely done with the struct we need to clean up the
     mutex and condition variable */
  pthread_mutex_destroy(&threadId.mtx);
  pthread_cond_destroy(&threadId.cond);
}

在上面的代码中,互斥锁和条件等待是必要的,以确保主线程在新线程设置TID之前不会尝试打印它。主线程启动新线程后等待,直到新线程完成存储TID的操作,然后新线程发出信号,使得主线程可以继续执行。

回答不错,但是这行代码 "if (pthread_create(&thread, NULL, foo, &threadId))" 应该改为 "if (pthread_create(&thread, NULL, foo, (void*)&threadId))"。请注意强制类型转换。 - splunk
@GreenMamba pthread_create()的原型指定了void *,所以它应该被隐式转换,不需要强制转换。 - Dmitri

0

人们通常感兴趣的有三个信息:进程 ID、线程 ID 和 pthreads 线程 ID。所有 pthread 调用都是自包含的,因为它们使用自己的线程 ID。进程 ID 和操作系统线程 ID 可能对 pthreads API 之外的原因很重要。

pthread_createpthread_self 报告 pthread ID,前者报告创建的线程,后者报告自身。否则就是一个先有鸡还是先有蛋的问题。一个线程不能询问另一个线程的 ID,除非它已经知道它的 ID。如果这很重要,必须构建一些机制(全局列表、某些 IPC 等)来实现它。

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <string.h>

typedef struct
{
    int       i;
    pid_t     pid;   // linux pid
    pid_t     tid;   // linux thread id
    pthread_t ptid;  // pthreads tid    
} data;

void *foo(void *args)
{
    data *p = (data *) args;

    p->pid  = getpid();
    p->tid  = syscall(SYS_gettid);
    p->ptid = pthread_self();

    return(p);
}

void spawnThreads(unsigned int numThreads)
{
    int ret;
    pthread_t *tids = malloc(sizeof(pthread_t) * numThreads);

    int i;

    for (i = 0; i < numThreads; i++)
    {
        data *dp = malloc(sizeof(data) * numThreads);
        memset(dp, '\0', sizeof(*dp));

        dp->i = i;

        ret = pthread_create(&tids[i], NULL, foo, (void *) dp);

        if ( ret != 0)
            perror("pthread create error");
    }

    for (int i = 0; i < numThreads; ++i)
    {
        data *status;

        ret = pthread_join(tids[i], (void *) &status);

        if ( ret != 0)
            perror("pthread join error");
        else
        {
            printf("thread num %d joined and reports pthreadId of %lu "
                   "process pid of %d and linux tid of %d\n",
                   status->i, status->ptid, status->pid, status->tid);

            free(status);
        }
    }

    free(tids);
}

int main(int argc, char *argv[])
{
    printf("main thread reports pthreadId of............ %lu "
           "process pid of %d and linux tid of %ld\n",
           pthread_self(), getpid(), syscall(SYS_gettid));

    spawnThreads(5);

    return (0);
}

一方面,这段代码允许线程并发运行,并为每个线程保留单独的结构体,这是很好的...但它也无法防止主线程(或其他线程)在数据有效之前尝试使用它。 - Dmitri
它也不能防止青少年怀孕,或者让初学者在编写第一个线程程序的时候负担学习使用条件变量。 - Duck
@Duck:我有一个关于pthread_join的问题。你调用它的方式是在创建所有线程之后单独使用一个for循环。但如果你在第一个for循环中调用pthread_join会有什么区别呢? - user2965601
@user2965601,pthead_join会阻塞线程,使得创建一个线程然后等待它完成,再创建...等待,如此循环。这样你就永远不会有多个线程同时运行。 - Duck

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