主线程和工作线程初始化

时间:2017-03-09 14:41:29

标签: c multithreading pthreads libc

我在C中创建一个多线程程序,我遇到了一些麻烦。 你有创建线程的函数:

import datetime

last_updated = db.Column(db.DateTime, default=datetime.datetime.now())

这里有我的args结构:

void        create_thread(t_game_data *game_data)
{
  size_t    i;
  t_args    *args = malloc(sizeof(t_args));

  i = 0;
  args->game = game_data;
  while (i < 10)
    {
      args->initialized = 0;
      args->id = i;
      printf("%zu CREATION\n", i);//TODO: Debug
      pthread_create(&game_data->object[i]->thread_id, NULL, &do_action, args);
      i++;
      while (args->initialized == 0)
          continue;
    }
} 

最后,处理创建的线程的函数

typedef struct      s_args
{
  t_game_data       *object;
  size_t            id;
  int               initialized;

}args;

问题是:

主线程将比新线程初始化变量更快地创建新线程:

void        *do_action(void *v_args)
{
  t_args    *args;
  t_game_data   *game;
  size_t    id;
  args = v_args;
  game = args->game;
  id = args->id;
  args->initialized = 1;

[...]

  return (NULL);
}

因此,有时,2个不同的线程将从args = v_args; game = args->game; id = args->id; 获得相同的id。 为了解决这个问题,我使用变量args->id作为bool,因此在新线程初始化期间使主线程“休眠”。

但我认为这真的很有罪。 也许有一种方法可以用互斥量做到这一点?但我听说解锁一个不属于他的线程的互斥锁并不“合法”。

感谢您的回答!

4 个答案:

答案 0 :(得分:2)

解决此问题的最简单方法是将不同的t_args对象传递给每个新线程。为此,在循环内移动分配,并使每个线程负责释放自己的参数struct:

void create_thread(t_game_data *game_data) {
    for (size_t i = 0; i < 10; i++) {
        t_args *args = malloc(sizeof(t_args));

        if (!args) {
            /* ... handle allocation error ... */
        } else {
            args->game = game_data;
            args->id = i;
            printf("%zu CREATION\n", i);//TODO: Debug
            if (pthread_create(&game_data->object[i]->thread_id, NULL,
                    &do_action, args) != 0) {
                // thread creation failed
                free(args);
                // ... 
            }
        }
    }
} 

// ...

void *do_action(void *v_args) {
    t_args *args = v_args;
    t_game_data *game = args->game;
    size_t    id = args->id;

    free(v_args);
    args = v_args = NULL;

    // ...

    return (NULL);
}

但你也写道:

  

为了解决这个问题,我使用一个初始化为bool的变量,所以make&#34; sleep&#34;   新线程初始化期间的主线程。

     

但我认为这真的很有罪。也许有办法做到这一点   用互斥量?但我听说它不是合法的&#34;解锁一个互斥锁   不属于他的主题。

如果您仍然希望一个线程等待另一个线程修改某些数据(正如您的原始策略所要求的那样),那么您必须使用原子数据或某种同步对象。您的代码否则包含数据争用,因此具有未定义的行为。在实践中,您不能在原始代码中假设主线程将看到新线程写入args->initialized。 &#34;声色&#34;这是一种不寻常的描述方式,但如果你属于神圣教会,也许是恰当的。

您可以使用互斥锁来解决该问题,只需使用互斥锁保护循环中args->initialized的测试 - 而不是整个循环 - 并保护线程&#39;使用相同的互斥锁写入该对象,但这是令人讨厌和丑陋的。等待新线程增加信号量(不是繁忙的等待,initialized变量被信号量替换),或设置并等待条件变量(再次不是忙等待,但仍需要initialized变量或等效变量。)

答案 1 :(得分:1)

问题是在create_thread中,您将相同的t_args结构传递给每个线程。实际上,您可能希望为每个线程创建自己的t_args结构。

发生的事情是你的第一个线程正在启​​动传递给它的args。在该线程可以运行do_action之前,循环正在修改args结构。由于thread2和thread1都指向相同的args结构,因此当它们运行do_action时,它们将具有相同的id。

哦,别忘了不要忘记记忆

答案 2 :(得分:0)

您应该考虑使用条件变量。你可以在http://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_28.html找到一个例子。 基本上在主线程中等待并在其他线程中发出信号。

答案 3 :(得分:0)

除了几个主要问题之外,你的解决方案应该在理论上有效。

  • 主线程将在while循环中旋转,使用CPU周期检查标志(这是最不好的问题,如果你知道它不需要等待很长时间就可以了)
  • 编译器优化器可以对空循环感到高兴。他们通常也不会意识到变量可能会被其他线程修改,并且可能在此基础上做出错误的决定。
  • 在多核系统上,主线程可能永远不会看到对args->initiialzed的更改,或者至少在更晚之后才会看到更改是在另一个尚未刷新回主内存的内核的缓存中

你可以使用John Bollinger的解决方案,为每个线程mallocs一组新的args,它很好。唯一的缺点是每个线程创建的malloc / free对。另一种方法是使用像Santosh建议的“适当”同步功能。我可能会考虑这个,除非我使用信号量比条件变量更简单。

信号量是一个原子计数器,有两个操作:等待和信号。如果信号量的值大于零,则等待操作会递减信号量,否则会使线程进入等待状态。信号操作会增加信号量,除非有线程在等待它。如果有,它会唤醒其中一个线程。

因此,解决方案是创建一个初始值为0的信号量,启动线程并等待信号量。然后线程在完成初始化时发信号通知信号量。

#include <semaphore.h>

// other stuff

sem_t semaphore;
void create_thread(t_game_data *game_data)
{
     size_t    i;
     t_args    args;

     i = 0;
     if (sem_init(&semaphore, 0, 0) == -1) // third arg is initial value
     {
         // error
     }
     args.game = game_data;
     while (i < 10)
     {
         args.id = i;
         printf("%zu CREATION\n", i);//TODO: Debug
         pthread_create(&game_data->object[i]->thread_id, NULL, &do_action, args);
         sem_wait(&semaphore);
         i++;
    }
    sem_destroy(&semaphore);
}

void *do_action(void *v_args) {
    t_args *args = v_args;
    t_game_data *game = args->game;
    size_t    id = args->id;
    sem_post(&semaphore);

    // Rest of the thread work

    return NULL;
} 

由于同步,我可以安全地重用args结构,事实上,我甚至不需要malloc它 - 它很小,所以我声明它是函数的本地。

说了这么多,我仍然认为John Bollinger的解决方案对于这个用例更好,但一般都要注意信号量。