C - Pthreads 线程进入无限循环,除非我在两者之间打印输出

问题描述

#include <pthread.h>
#include <stdio.h>

#define WIDTH   1000
#define HEIGHT  1000

typedef struct  s_pair
{
    int x;
    int y;
}               t_pair;

char    pixels[HEIGHT][WIDTH];

void    *add_to_global(void *param)
{
    t_pair  *input;

    input = (t_pair *)param;
    if (!(input->x % 2))
        pixels[input->y][input->x] = '.';
    else if (!(input->x % 3))
        pixels[input->y][input->x] = '#';
    else
        pixels[input->y][input->x] = ' ';
    return (NULL);
}

void    thread_runner(int x_start,int x_end,int y_start,int y_end)
{
    pthread_t   workers[10];
    t_pair      inputs[10];
    int         i,x,y;

    y = y_start - 1;
    while (++y < y_end)
    {
        x = x_start - 1;
        i = -1;
        while (x < x_end)
        {
            if (++i < 10)
            {
                inputs[i] = (t_pair){++x,y};
                pthread_create(&workers[i],NULL,add_to_global,(void *)&inputs[i]);
            }
            else
                while (--i > -1)
                    pthread_join(workers[i],NULL);
        }
        while (--i > -1)
            pthread_join(workers[i],NULL);
        printf("%s\n",pixels[y]); // if this is removed,this program will never finish
    }
}

int         main()
{
    thread_runner(0,WIDTH,HEIGHT);
    return (0);
}

因此,最初在尝试制作分形可视化器时遇到了这个问题,只有当我在屏幕上绘制图像时,我才能使用这种一次运行 10 个线程的方法。如果我让程序自己运行,它会变得完全没有响应,我的 mac 开始发出令人不安的噪音。我对线程还是个新手,所以这可能是我对它们的误解很简单。

这个程序是我尝试在不需要所有分形废话的情况下重现这种现象。这是一个完全没有意义的程序,它只是告诉你一个点的 x 坐标是否可以被 2 或 3 整除,并相应地将某些字符打印到终端。

如果在处理完该行上的每个点 x 后删除打印每一行 y 的 printf,程序将变得无响应。但是,通过这种定期打印运行程序可以使其工作。这似乎是一些编程恶魔在和我玩诡计,使问题无法调试。

为什么这个问题被一个看似毫无意义的 printf 解决了?为什么我的 mac 在没有这个 printf 的情况下运行时会发出令人不安的声音?

解决方法

您正在读取/写入数组的末尾。

    x = x_start - 1;
    i = -1;
    while (x < x_end)
    {
        if (++i < 10)
        {
            inputs[i] = (t_pair){++x,y};
            pthread_create(&workers[i],NULL,add_to_global,(void *)&inputs[i]);
        }
        else
            while (--i > -1)
                pthread_join(workers[i],NULL);
    }

在这个块中,外循环的最后一次迭代有 x 等于 x_end-1。然后,当您创建输入时,x 会递增,因此 inputs[i].xx_end。然后使用该值写入数组维度之一的末尾。

这会触发 undefined behavior,您将其视为对 printf 的调用导致/防止无限循环。

不是以 -1 开始循环并在循环条件或主体中间递增,而是从 0 开始并在结束时递增。

void thread_runner(int x_start,int x_end,int y_start,int y_end)
{
    pthread_t   workers[10];
    t_pair      inputs[10];
    int         i,x,y;

    y = 0;
    while (y < y_end)
    {
        i = 0;
        x = 0;
        while (x < x_end)
        {
            if (i < 10)
            {
                inputs[i] = (t_pair){x,y};
                pthread_create(&workers[i],&inputs[i]);
                i++;
            }
            else {
                while (--i > -1)
                    pthread_join(workers[i],NULL);
                i=0;
            }
            x++;
        }
        while (--i > -1)
            pthread_join(workers[i],NULL);
        y++;
    }
}

我喜欢用来查找此类问题的一个技巧(我在本例中使用)是将所有固定数组更改为动态分配的内存。这有两件事:如果你做错了什么,它更多可能会崩溃(更容易找到它们),并且它更好地允许像 valgrind 这样的工具准确地检测正在发生的事情。

,

您只在 ++x 上执行 if,但不是 else。因此,whilex 循环可能永远不会完成。

没有看到允许 printf 改变线程的时间和交互的竞争条件,所以我有点不明白为什么你认为它有帮助完成。当我最初运行您的代码时(使用 printf),我仍然遇到了无限循环。


编辑: 仔细查看后,我想出的修复不能正确表示您的循环。它阻止了无限循环,但没有没有覆盖所需的 x 范围。下面有一个更好的修复。

您的 pthread_join 循环有通用代码。通过组合它并添加一个 break,我 [至少] 完成了。

这可能不是您的全部/最终解决方案,但可能会有所帮助:

#include <pthread.h>
#include <stdio.h>

#define WIDTH   1000
#define HEIGHT  1000

typedef struct s_pair {
    int x;
    int y;
} t_pair;

char pixels[HEIGHT][WIDTH];

void *
add_to_global(void *param)
{
    t_pair *input = param;

    if (! (input->x % 2))
        pixels[input->y][input->x] = '.';
    else if (! (input->x % 3))
        pixels[input->y][input->x] = '#';
    else
        pixels[input->y][input->x] = ' ';

    return (NULL);
}

void
thread_runner(int x_start,int y_end)
{
    pthread_t workers[10];
    t_pair inputs[10];
    int i,y;

    y = y_start - 1;

    while (++y < y_end) {
        x = x_start - 1;
        i = -1;

        while (x < x_end) {
            if (++i >= 10)
                break;

            inputs[i] = (t_pair) { ++x,y };
            pthread_create(&workers[i],&inputs[i]);
        }

        while (--i > -1)
            pthread_join(workers[i],NULL);

        // if this is removed,this program will never finish
        printf("%s\n",pixels[y]);
    }
}

int
main(void)
{
    thread_runner(0,WIDTH,HEIGHT);
    return (0);
}

更新:

这里有一个更好的解决方法。

-1 开始的循环(例如 y = y_start - 1)有点不常见,它们会使代码复杂化,并且可能会引入一个错误。

#include <pthread.h>
#include <stdio.h>

#define WIDTH   1000
#define HEIGHT  1000

typedef struct s_pair {
    int x;
    int y;
} t_pair;

char pixels[HEIGHT][WIDTH];

void *
add_to_global(void *param)
{
    t_pair *input = param;

    if (! (input->x % 2))
        pixels[input->y][input->x] = '.';
    else if (! (input->x % 3))
        pixels[input->y][input->x] = '#';
    else
        pixels[input->y][input->x] = ' ';

    return (NULL);
}

void
thread_join(int i,pthread_t *workers)
{

    while (--i > -1)
        pthread_join(workers[i],NULL);
}

void
thread_runner(int x_start,y;

    for (y = y_start;  y < y_end;  ++y) {
        x = x_start;

        while (x < x_end) {
            for (i = 0;  (i < 10) && (x < x_end);  ++i,++x) {
                inputs[i] = (t_pair) { x,y };
                pthread_create(&workers[i],&inputs[i]);
            }
            thread_join(i,workers);
            i = 0;
        }

        // NOTE: not really needed -- just to be safe
        thread_join(i,workers);

        // if this is removed,this program will never finish
#if 0
        printf("%s\n",pixels[y]);
#else
        printf("y=%d x=%d x_end=%d\n",y,x_end);
#endif
    }
}

int
main(void)
{
    thread_runner(0,HEIGHT);
    return (0);
}