写入数组时,最后一个线程的执行速度比第一个线程慢

时间:2016-01-06 21:45:32

标签: c++ arrays multithreading multidimensional-array mandelbrot

我正在尝试优化Mandelbrot集生成器,问题是我试图通过使用_beginthread()函数使其成为多线程。我正在解决的计算问题是在2D平面上运行一个函数,我试图同时运行大约8个线程,每个线程计算2D阵列的一部分(行),但我注意到第一个线程完成后,完成的最后一个线程完成得快得多。这是输出:

Starting thread 0
Starting thread 1
Starting thread 2
Starting thread 3
Starting thread 4
Starting thread 5
Starting thread 6
Starting thread 7
Ending thread   0 - Time taken: 1062ms
Ending thread   7 - Time taken: 1031ms
Ending thread   1 - Time taken: 1610ms
Ending thread   6 - Time taken: 1563ms
Ending thread   2 - Time taken: 10265ms
Ending thread   5 - Time taken: 10219ms
Ending thread   4 - Time taken: 31609ms
Ending thread   3 - Time taken: 31641ms

每个线程都有相同的事情要做,但是由于数字不同,我不明白为什么我会这么做 这就是我多线程的方式:

#define HEIGHT 4000
#define WIDTH 4000
#define MAX_THREADS 8
int const maxIterations = 150;

int bitmap[HEIGHT][WIDTH];
bool finishedThreads[MAX_THREADS];

void renderRow(void * arg) {
    int startTime = GetTickCount();
    int * threadNumPinter = (int*)arg;
    int threadNum = *threadNumPinter;
    int startRow = threadNum * (HEIGHT / MAX_THREADS);
    for (int y = startRow; y <= startRow+(HEIGHT / MAX_THREADS); y++) {
        for (int x = 0; x <= WIDTH; x++) {
            double xx = (((double)x / (double)WIDTH) * 4.0) - 2.0;
            double yy = (((double)y / (double)HEIGHT) * 4.0) - 2.0;
            bitmap[x][y] = isPartOfSet(xx, yy) * 10;
        }
    }
    threadNum = startRow / (HEIGHT / MAX_THREADS);
    finishedThreads[threadNum] = true;
    cout << "Ending thread " << threadNum << " - Time: " << GetTickCount() - startTime << "ms" << endl;
    _endthread();
}


int main() {
    int startTime = GetTickCount();
    HANDLE hThread;
    HANDLE ghEvents[2];
    DWORD dwThreadID;
    int rowsPerThread = HEIGHT / MAX_THREADS;
    int arg;
    int threadIds[MAX_THREADS];
    for (int i = 0; i < MAX_THREADS; i ++) {
        threadIds[i] = i;
        cout << "Starting thread " << i << endl;
        arg = i;
        _beginthread(renderRow, 0, &threadIds[i]);
        Sleep(10);
    }
    bool done = true;//Wait for all threads to finish
    while (1) {
        for (int i = 0; i < MAX_THREADS; i++){
            if (finishedThreads[i] == false)done = false;
        }
        if (done == true) break;
        else done = true;
        Sleep(20);
    }
    saveBitmap(WIDTH, HEIGHT);
    cout << endl << "Rendered in " << double(GetTickCount() - startTime) / 1000.0 << " seconds" << endl;
    cin.get();
    main();
}

显然有更多的代码,但我不认为它对这个问题有任何影响。我在这做错了什么?我在CUDA上遇到了同样的问题,所以我相信这是我实现多线程的方式。感谢。

3 个答案:

答案 0 :(得分:5)

在我的回答中,我不会解决线程/同步问题或缓存问题 - 请参阅其他答案/评论。

我的观点与众不同:你写的是&#34;每个主题都有相同的事情要做,但是数字不同&#34; 。如果我对mandelbrot集合的记忆对我有用,那么确定一个点是否是该集合的成员(IOW你的isPartOfSet函数的实现,你没有提供)是一个迭代过程。一些要点&#34;拯救&#34;很快,有些要点,你必须继续迭代,直到你预定义的最大数量为止。

所以我所说的是:用你的&#34;每个线程一个大块&#34;并行化,你的线程可能会花费大量不同的时间。

解决这类问题的方法是将问题(即图像)拆分成较小的部分,其大小取决于线程的数量,但应根据经验选择a)不要太大,以防止不平等的工作分配(如你的巨大块的例子)和b)不小到导致过多的组织开销。

所以现在,你有M个线程和N个工作块(N&gt;&gt; M),你需要一个让每个线程都能在循环中工作的实现,如

while (worktodo) fetch_a_chunk_of_work_and_do_it ()

如何实施这种生产者/消费者模式 - 我会留下让其他人描述(或者为了谷歌: - ))

答案 1 :(得分:1)

incorret并发使用全局变量的经典示例。

bool finishedThreads[MAX_THREADS];

是全局的,可以从多个线程(写/读)访问。你不能指望这个工作。对于您的情况,您甚至不应该使用此变量。相反,您应该等待线程完成事件。

答案 2 :(得分:0)

硬编码到8个线程是可怕的,一些用户的双核笔记本电脑怎么样? std::thread::hardware_concurrency

睡眠很糟糕。你的旋转循环绝对不是正确的方法。对不起,说实话。

使用std::thread并使用join等待它们完成。更好的是:在其他线程上执行除一个工作项之外的所有工作项,在主线程上执行一个,然后加入其他线程。如果有N个CPU,则应创建N-1个线程并在主线程上执行一个项目。

为什么在有更好的标准C ++库类时使用仅使用Windows的API?

避免Sleep

的建议方法

如果仅仅等待线程退出是不够的(使用上面提到的连接),在更复杂的场景中,那么您应该使用std::mutexstd::unique_lockstd::condition_variable

当通知发生时,您应该有一个设置为true的变量。在等待的代码中,您获取互斥锁,检查该标志,如果没有设置,则在条件变量上调用wait

在通知另一个线程的线程中,获取互斥锁,设置我提到的标志变量,在条件变量上使用notify_onenotify_all方法。

查看此reference on cppreference。你使用的主要是我已经提到的那些。