主线程和工作线程初始化
Main thread and worker thread initialization
我正在用 C 语言创建一个多线程程序,但遇到了一些麻烦。
那里有创建线程的函数:
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;
}
}
这是我的 args 结构:
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);
}
问题是:
主线程创建新线程的速度比新线程初始化变量的速度快:
args = v_args;
game = args->game;
id = args->id;
所以,有时,2 个不同的线程会从 args->id
获得相同的 id
。
为了解决这个问题,我使用变量 initialized
作为布尔值,因此在新线程初始化期间使 "sleep" 成为主线程。
但我认为那真的很罪恶。
也许有一种方法可以用互斥锁来做到这一点?但是我听说解锁一个不属于他的线程的互斥体不是"legal"。
感谢您的回答!
您应该考虑为此使用条件变量。您可以在此处找到示例 http://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_28.html。
基本上在主线程中等待并在其他线程中发出信号。
问题在于,在 create_thread
中,您将相同的 t_args
结构传递给每个线程。实际上,您可能想为每个线程创建自己的 t_args 结构。
您的第一个线程正在使用传递给它的 args 启动。在该线程可以 运行 do_action
循环修改 args 结构之前。由于 thread2 和 thread1 都将指向相同的 args 结构,因此当它们 运行 do_action
时它们将具有相同的 id。
哦,别忘了不要泄露你的记忆
解决此问题的最简单方法是将不同的 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);
}
但你也写:
To solve that, I use an variable initialized as a bool so make "sleep"
the main thread during the new thread's initialization.
But I think that is really sinful. Maybe there is a way to do that
with a mutex? But I heard it wasn't "legal" to unlock a mutex which
does not belong his thread.
如果您仍然希望一个线程等待另一个线程修改某些数据,正如您最初的策略所要求的那样,那么您必须使用原子数据或某种同步对象。您的代码否则包含数据竞争,因此具有未定义的行为。实际上,您不能在原始代码中假设主线程将 永远 看到新线程对 args->initialized
的写入。 "Sinful" 是一种不寻常的描述方式,但如果您属于圣 C 教会,则可能是合适的。
你可以用互斥来解决这个问题,方法是在你的循环中只保护 args->initialized
的测试 - 而不是整个循环 - 用互斥锁保护线程对该对象的写入相同的互斥量,但那是令人讨厌和丑陋的。最好等待新线程递增信号量(不是忙等待,并且 initialized
变量被信号量替换),或者设置并等待条件变量(同样不是忙等等,但仍然需要 initialized
变量或等效变量)。
除了几个主要问题外,您的解决方案在理论上应该可行。
- 主线程将在使用 CPU 周期检查标志的 while 循环中旋转(这是最不严重的问题,如果您知道它不必等待很长时间就可以了)
- 编译器优化器可以让触发器对空循环感到满意。他们通常也不知道变量可能会被其他线程修改,并可能在此基础上做出错误的决定。
- 在多核系统上,主线程可能永远不会看到对
args->initiialzed
的更改,或者至少要等到很久以后,如果更改在另一个尚未刷新回主线程的核心的缓存中记忆犹新
您可以使用 John Bollinger 的解决方案,为每个线程 malloc 一组新的 args,这很好。唯一的缺点是每个线程创建一个 malloc/free 对。另一种方法是像 Santosh 建议的那样使用 "proper" 同步函数。我可能会考虑这一点,除非我会使用信号量,因为它比条件变量更简单。
信号量是具有两个操作的原子计数器:等待和信号。如果信号量的值大于零,则等待操作递减信号量,否则将线程置于等待状态。 signal 操作会增加信号量,除非有线程在等待它。如果有,它会唤醒其中一个线程。
因此解决方案是创建一个初始值为 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 的解决方案更适合这个用例,但通常了解信号量很有用。
我正在用 C 语言创建一个多线程程序,但遇到了一些麻烦。 那里有创建线程的函数:
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;
}
}
这是我的 args 结构:
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);
}
问题是:
主线程创建新线程的速度比新线程初始化变量的速度快:
args = v_args;
game = args->game;
id = args->id;
所以,有时,2 个不同的线程会从 args->id
获得相同的 id
。
为了解决这个问题,我使用变量 initialized
作为布尔值,因此在新线程初始化期间使 "sleep" 成为主线程。
但我认为那真的很罪恶。 也许有一种方法可以用互斥锁来做到这一点?但是我听说解锁一个不属于他的线程的互斥体不是"legal"。
感谢您的回答!
您应该考虑为此使用条件变量。您可以在此处找到示例 http://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_28.html。 基本上在主线程中等待并在其他线程中发出信号。
问题在于,在 create_thread
中,您将相同的 t_args
结构传递给每个线程。实际上,您可能想为每个线程创建自己的 t_args 结构。
您的第一个线程正在使用传递给它的 args 启动。在该线程可以 运行 do_action
循环修改 args 结构之前。由于 thread2 和 thread1 都将指向相同的 args 结构,因此当它们 运行 do_action
时它们将具有相同的 id。
哦,别忘了不要泄露你的记忆
解决此问题的最简单方法是将不同的 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);
}
但你也写:
To solve that, I use an variable initialized as a bool so make "sleep" the main thread during the new thread's initialization.
But I think that is really sinful. Maybe there is a way to do that with a mutex? But I heard it wasn't "legal" to unlock a mutex which does not belong his thread.
如果您仍然希望一个线程等待另一个线程修改某些数据,正如您最初的策略所要求的那样,那么您必须使用原子数据或某种同步对象。您的代码否则包含数据竞争,因此具有未定义的行为。实际上,您不能在原始代码中假设主线程将 永远 看到新线程对 args->initialized
的写入。 "Sinful" 是一种不寻常的描述方式,但如果您属于圣 C 教会,则可能是合适的。
你可以用互斥来解决这个问题,方法是在你的循环中只保护 args->initialized
的测试 - 而不是整个循环 - 用互斥锁保护线程对该对象的写入相同的互斥量,但那是令人讨厌和丑陋的。最好等待新线程递增信号量(不是忙等待,并且 initialized
变量被信号量替换),或者设置并等待条件变量(同样不是忙等等,但仍然需要 initialized
变量或等效变量)。
除了几个主要问题外,您的解决方案在理论上应该可行。
- 主线程将在使用 CPU 周期检查标志的 while 循环中旋转(这是最不严重的问题,如果您知道它不必等待很长时间就可以了)
- 编译器优化器可以让触发器对空循环感到满意。他们通常也不知道变量可能会被其他线程修改,并可能在此基础上做出错误的决定。
- 在多核系统上,主线程可能永远不会看到对
args->initiialzed
的更改,或者至少要等到很久以后,如果更改在另一个尚未刷新回主线程的核心的缓存中记忆犹新
您可以使用 John Bollinger 的解决方案,为每个线程 malloc 一组新的 args,这很好。唯一的缺点是每个线程创建一个 malloc/free 对。另一种方法是像 Santosh 建议的那样使用 "proper" 同步函数。我可能会考虑这一点,除非我会使用信号量,因为它比条件变量更简单。
信号量是具有两个操作的原子计数器:等待和信号。如果信号量的值大于零,则等待操作递减信号量,否则将线程置于等待状态。 signal 操作会增加信号量,除非有线程在等待它。如果有,它会唤醒其中一个线程。
因此解决方案是创建一个初始值为 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 的解决方案更适合这个用例,但通常了解信号量很有用。