【问题标题】:Multithreading using WinAPI. Calculate PI使用 WinAPI 的多线程。计算 PI
【发布时间】:2017-06-20 10:01:12
【问题描述】:

我的作业需要使用多个线程计算 PI。问题是——当使用多个线程时,大多数情况下总和计算不正确(小于应有的值)。我花了很长时间来弄清楚它,所以也许我忽略了一些明显的东西。希望得到您的帮助。

附:有一个关于同一任务的问题。但是,我的错误是不同的。

这是我的代码。

#include <stdio.h>
#include <Windows.h>
#include <ctime>

#define THREAD_COUNT 2
#define N 10000000
#define BLOCK_SIZE 43087

typedef struct {
    unsigned startPos;
    int threadInd;
    double threadSum;
    bool isCalculating;
} ThreadArg;

HANDLE* threads;
HANDLE* events;

DWORD WINAPI ThreadProc(LPVOID);
void calculate(double* sum, unsigned start);

int main() {
    unsigned cursor = 0;
    ThreadArg* params = NULL;
    threads = NULL;

    if (THREAD_COUNT > 1) {
        params = (ThreadArg*)malloc(sizeof(ThreadArg)*(THREAD_COUNT - 1));
        threads = (HANDLE*)malloc(sizeof(HANDLE)*(THREAD_COUNT - 1));
        events = (HANDLE*)malloc(sizeof(HANDLE)*(THREAD_COUNT - 1));
    }
    for (int i = 0; i < THREAD_COUNT - 1; ++i) {
        ThreadArg arg;
        arg.isCalculating = true;
        arg.startPos = cursor;
        arg.threadInd = i;
        arg.threadSum = 0;
        params[i] = arg;
        threads[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)(&params[i]), CREATE_SUSPENDED, 0);
        events[i] = CreateEvent(NULL, TRUE, TRUE, NULL);
        cursor += BLOCK_SIZE;
    }

    unsigned startTime = clock();
    for (int i = 0; i < THREAD_COUNT - 1; ++i) {
        ResumeThread(threads[i]);
    }

    double mainSum = 0;
    while (cursor < N) {
        for (int i = 0; i < THREAD_COUNT - 1; ++i) {
            if (!params[i].isCalculating) {
                params[i].startPos = cursor;
                SetEvent(events[i]);
                cursor += BLOCK_SIZE;
            }
        }
        calculate(&mainSum, cursor);
        cursor += BLOCK_SIZE;
    }

    bool allFinished;
    do {
        allFinished = true;
        for (int i = 0; i < THREAD_COUNT - 1; ++i) {
            if (params[i].isCalculating) {
                allFinished = false;
                break;
            }
        }
    } while(!allFinished);

    for (int i = 0; i < THREAD_COUNT - 1; ++i) {
        mainSum += params[i].threadSum;
        CloseHandle(threads[i]);
    }

    printf("Time: %d\n", clock() - startTime);
    printf("PI = %.7f\n", mainSum/N);
    free(threads);
    free(params);
    system("pause");
}

DWORD WINAPI ThreadProc(LPVOID lParam) {
    ThreadArg* param = (ThreadArg*)lParam;
    while (true) {
        ResetEvent(events[param->threadInd]);
        calculate(&param->threadSum, param->startPos);
        InterlockedDecrement((LONG*)&param->isCalculating);
        WaitForSingleObject(events[param->threadInd], INFINITE);
        InterlockedIncrement((LONG*)&param->isCalculating);
    }
}

void calculate(double* sum, unsigned start) {
    int endLoop = start + BLOCK_SIZE;
    double x;
    for (unsigned i = start; i < endLoop; ++i) {
        if (i < N) {
            x = (i + 0.5) / N;
            *sum += 4 / (1 + x*x);
        } else break;
    }
}

【问题讨论】:

  • 你试过在没有线程的情况下运行相同的代码吗? IE。将ResumeThread 调用替换为ThreadProc 的直接调用,并检查一切是否正常。
  • 它似乎工作正常。当所有计算都发生在主线程中时也可以正常工作。它在接近计算结束时开始出现故障,因为结果在小数点后的第一位或第二位附近波动
  • 那么它肯定看起来像竞态条件,这可能是由于线程之间缺乏适当的同步造成的。我可以看到您使用了一些变量/字段,例如 isCalculatingsome 同步甚至原子操作(对于那种问题来说这听起来太难了)。我建议您为每个线程分配一个事件(“完成计算”),然后在主线程中使用 WaitForMultipleObjects 代替 for 循环。
  • @yeputons 不知道为什么我之前没用过。现在可以用了,非常感谢
  • 这……不是一个明智的任务。你处于一个令人羡慕的位置。在这个问题上抛出多个线程实际上并没有什么好处,而且在同步错误和消除这些错误所涉及的不可避免的减速方面会损失很多。计算 pi 不适合这种类型的并行化。开销会扼杀任何可能的性能提升,可能会增加很多倍。

标签: c multithreading winapi


【解决方案1】:

你根本不需要事件。工作线程永远不必等待。一切都可以通过联锁操作来完成。

class CALC_TASK 
{
    C_ASSERT(sizeof(double)==sizeof(__int64));

    union {
        double _sum;
        __int64 _value;
    };

    double _step;
    LONG _startPos, _maxPos, _blockSize;

    static double calculate(LONG i, LONG count, double step) 
    {  
        double x, sum = 0.0;  

        do 
        {
            x = (i-- - 0.5) * step;  
            sum += 4.0 / (1.0 + x*x);  
        } while (--count);

        return step * sum;  
    } 

    void add(double x)
    {
        union {
            double sum;
            __int64 value;
        };

        union {
            double new_sum;
            __int64 new_value;
        };

        sum = _sum;

        for ( ; ; value = new_value)
        {
            new_sum = sum + x;

            new_value = _InterlockedCompareExchange64(&_value, new_value, value);

            if (new_value == value)
            {
                return;
            }
        }
    }

    BOOL getBlock(LONG& pos, LONG& count)
    {
        LONG startPos, newPos, curPos;

        for (startPos = _startPos ; startPos < _maxPos; startPos = curPos)
        {
            if ((newPos = startPos + _blockSize) > _maxPos)
            {
                newPos = _maxPos;
            }

            curPos = _InterlockedCompareExchange(&_startPos, newPos, startPos);

            if (curPos == startPos)
            {
                pos = newPos, count = newPos - startPos;
                return TRUE;
            }
        }

        return FALSE;
    }

    void calculateEx()
    {
        LONG i, count;

        double step = _step;

        while (getBlock(i, count))
        {
            add(calculate(i, count, step));
        }
    }

public:

    double getsum()
    {
        return _sum;
    }

    CALC_TASK(LONG maxPos, LONG blockSize)
    {
        _startPos = 0, _blockSize = blockSize, _maxPos = maxPos, _sum = 0.0, _step = 1.0 / (double)maxPos;
    }

    static DWORD CALLBACK ThreadProc(PVOID pct)
    {
        reinterpret_cast<CALC_TASK*>(pct)->calculateEx();
        return 0;
    }
};

double test(LONG maxPos, LONG blockSize)
{
    SYSTEM_INFO si;
    GetNativeSystemInfo(&si);
    PHANDLE phThreads = 0;
    ULONG nThreads = 0;

    CALC_TASK ct(maxPos, blockSize);

    if (1 < si.dwNumberOfProcessors)
    {
        ULONG n = si.dwNumberOfProcessors - 1;

        phThreads = (HANDLE*)alloca(n * sizeof(HANDLE));

        do 
        {
            if (*phThreads = CreateThread(NULL, PAGE_SIZE, CALC_TASK::ThreadProc, &ct, 0, 0))
            {
                nThreads++, phThreads++;
            }
        } while (--n);
    }

    CALC_TASK::ThreadProc(&ct);

    if (nThreads)
    {
        WaitForMultipleObjects(nThreads, phThreads - nThreads, TRUE, INFINITE);
        do 
        {
            CloseHandle(*--phThreads);
        } while (--nThreads);
    }

    return ct.getsum();
}

void testEx()
{
    ULONG dwStart = GetTickCount();

    double d = test(0x10000000, 0x8000);

    dwStart = GetTickCount() - dwStart;

    DbgPrint("pi = %.15f, %u milliseconds\n", d, dwStart);
}

在我的 8 个核心/胎面测试中,0x10000000 步骤大约需要 0.5 秒。使用单线程这需要大约 3.5 秒。

您也可以使用openMP

double calculateOMP(int num_steps) 
{  
    double x, sum = 0.0, step = 1.0 / (double) num_steps;  

#pragma omp parallel for reduction(+:sum) private(x) 

    for (int i = num_steps; i > 0; --i)
    {
        x = (i - 0.5) * step;  
        sum += 4.0 / (1.0 + x*x);  
    }

    return step * sum;  
} 

为此,您需要使用/openmp 选项并将vcomp.lib 添加到链接器输入

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 2015-08-23
    • 1970-01-01
    • 2015-06-19
    • 1970-01-01
    • 2020-09-09
    • 2013-05-31
    相关资源
    最近更新 更多