0

因此,我正在尝试制作一个简单的多线程程序来验证 Collat​​z 猜想的大量数字并返回已验证数字的总数。每个线程(总共 4 个)执行一个数字间隔,并在数字达到 1 时更新“已验证”变量。我也在计时整个过程(与单线程计算进行比较)

我遇到的问题是,当我在程序结束时打印出“已验证”的 int 时,从来没有任何一致性,所以我猜要么线程正在相互写入,要么主线程之前完成其他人,因此打印一个不完整的数字。我还假设如果主线程在其他线程之前完成,clock() 计算也将关闭。那么,如何停止主线程继续运行,直到其他线程完成(从而使其等待更新的验证计数并完成准确的时间测量)?这就是我认为 WaitForSingleObject 所做的,但我猜它只是停止主线程退出,仍然允许它计算它的其他函数。

这是我第一次尝试任何多线程,我认为我不太了解同步和 WaitForSingleObject 命令的工作原理。到目前为止,这是我的主要功能中的内容:

编辑:这是我更新的 Main 函数和 Collat​​z 函数。我对其进行了修改,以便每个线程都访问一个单独的变量以避免同步问题,但问题仍然存在。当我打印出“已验证”时,没有一致的值 *

再次编辑:好的,所以我删除了每个 Mladen Janković 的“线程”int,并使用一个简单的计数器将不同的间隔分配给创建的线程。“已验证”现在有一个一致、正确的值。但是,当有 1,000,000 个数字时,我仍然无法让程序真正完成。测试它 100,000 甚至 10,000 可以完美地工作,但是当我将它增加到 1,000,000 个数字时,程序会无限期地运行(小时)而没有实际返回值。我猜它会卡在一个特定的值上(例如 Martin James 指出的 750831)。我尝试用 int 代替 long int,但似乎仍然存在溢出问题。有什么建议么?并感谢您的巨大帮助。

最后编辑:好的,所以我只是使用 long long 而不是 int,现在程序可以完美运行。感谢大家的帮助!

void main() 
{
    clock_t start;
    clock_t finish;
    unsigned int thread = 0;

    start = clock();

    HANDLE h1 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, NULL, 0, NULL);

    HANDLE h2 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, NULL, 0, NULL);

    HANDLE h3 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, NULL, 0, NULL);

    for (int i = 750001 ; i <= 1000000 ; i++) { collatz(i, 4); }

    WaitForSingleObject( h1, INFINITE );
    WaitForSingleObject( h2, INFINITE );
    WaitForSingleObject( h3, INFINITE );

    finish = clock() - start;
    double time = finish / (double) CLOCKS_PER_SEC;

    validated = v1 + v2 + v3 + v4;
    cout << validated << " numbers validated." << endl;
    cout << endl << time << " seconds." << endl;
}

unsigned _stdcall collatz_thread (void* n)
{   
    selection++; // selects a different interval each time collatz_thread is called

    switch (selection) {
    case 1:
        for (int i = 1 ; i <= 250000; i++)      { collatz(i, 1); }
        break;
    case 2:
        for (int i = 250001 ; i <= 500000; i++)  { collatz(i, 2); }
        break;
    case 3:
        for (int i = 500001 ; i <= 750000; i++)  { collatz(i, 3); }
        break;
    }
    return 0;
}

int collatz (int n, int thread)
{
    int original = n;

    while (n != 1) {
    if (n%2 == 0)
        n = (n/2);
    else
        n = (3*n + 1);
    }

    if (n == 1) {
    switch (thread) {
        case 1:
            v1++;
            break;
        case 2:
            v2++;
            break;
        case 3:
            v3++;
            break;
        case 4:
            v4++;
            break;
    }
    return n;
}

}

4

3 回答 3

1

validated如果它是共享变量,您需要同步访问。最简单的方法是当你想增加它时使用InterlockedIncrement函数而不是标准运算符。++另一种方法是在访问共享变量时使用某种同步对象,如自旋锁或互斥锁,但如果您只需要同步增量操作,那就太过分了。

如果您需要更多详细信息,请提供collatz功能代码。

正如“usr”所建议的那样,为了获得更好的性能,您可以为每个线程使用单独的变量,然后在主线程中将它们相加。在这种情况下,您应该以这样的方式填充这些变量,以便它们不共享相同的缓存行以避免错误共享

您没有提供collatz_thread函数,这可能是导致结果不一致的另一个原因。原因是您将指针传递给变量 ( &thread),该变量存储在创建新线程的调用之间更改的线程 #,因此根据操作系统调度程序的状态,新线程可能没有机会启动,而thread变量已经更改为另一个值,因此您将有多个线程执行同一组数据,而其他组可能会被跳过。由于行为取决于线程调度程序的当前状态,它几乎是不可预测的。

解决方案是将thread变量转换为,void*而不是传递其地址,然后在collatz_thread函数中将其转换回int

HANDLE h1 = (HANDLE)_beginthreadex(NULL, 0, collatz_thread, (void*)thread, 0, NULL);

正如马丁建议的那样,你可能有整数溢出,但它不应该导致不一致的结果,只是错误的结果,但仍然是一致的。

于 2012-04-15T17:51:35.157 回答
1

试着看看这个:

来自 MSDN 的信号量和线程说明

这可能是您在网上可以找到的最好的文档。

现在,关于您的代码,我认为它运行得不是很好,这就是原因:WaitForSingleObject - 基本上意味着您尝试在 h1 信号量(或 h2 或 h3)上执行 -1,如果您不能这样做-1(即信号量为0)然后等待无限时间。WaitForSimgleObject 实际上应该在您的线程例程中调用,而不是在您的主程序中。

此外,在您的线程对象中,一旦您完成了对共享变量的处理,您需要释放信号量,以便其他线程可以获得对该特定信号量的锁定。

试着看看我给你的链接上的例子,我相信你会让它工作得很快。

于 2012-04-15T18:13:14.630 回答
1

我对此进行了尝试,并得到了一些很好的结果,太好了 :(( 某处出了点问题,但我没有看到它。我没有得到几个小时的运行时间,即使 n 从 1 到10,000,000,(千万):

8 tests,
8 tasks,
counting to 1000000,
using 14 threads:
Validated: 1000000 in 670ms
Validated: 1000000 in 671ms
Validated: 1000000 in 624ms
Validated: 1000000 in 656ms
Validated: 1000000 in 655ms
Validated: 1000000 in 655ms
Validated: 1000000 in 640ms
Validated: 1000000 in 686ms
Average time: 657ms
Total validated: 8000000

8 tests,
8 tasks,
counting to 10000000,
using 14 threads:
Validated: 10000000 in 8081ms
Validated: 10000000 in 7441ms
Validated: 10000000 in 7784ms
Validated: 10000000 in 7598ms
Validated: 10000000 in 7956ms
Validated: 10000000 in 7534ms
Validated: 10000000 in 7816ms
Validated: 10000000 in 7769ms
Average time: 7747ms
Total validated: 80000000

请注意,虽然它说 14 个线程,但这是针对整个池的。一个线程总是在等待其他任务完成时用完,因此实际上只有 13 个线程可用于运行验证。出于某种原因,这是最佳选择。

好的,我的 i7 在所有 4/8 内核上都是完全正常的,但是我看不到运行时间缩短到几秒钟,因为我有更多内核并且已经将工作分给了所有内核:(

这是我用的。这与您的操作方式有些不同,因为我有大部分工具/代码。首先是 Visual C++。有两个班。每次运行都由一个“PoolTest”类管理,该类将几个“TestTask”实例提交到一个线程池,一个用于要验证的完整整数范围的每个段。您会注意到您的代码已复制/粘贴到 TestTask 类中。我强调了 TestTask 在 PoolTest 代码中的组装位置。'PoolTest' 类然后等待所有 'TestTask' 实例完成的事件 - 它可以这样做,因为 TestTask 在完成时回调到 PoolTest 'taskComplete' 方法。此方法访问一个线程安全计数器,该计数器在提交 TestTasks 时向上计数,并由 '

我重复使用的这段代码使事情变得有点复杂,因为它可以多次重复运行以获得平均时间,因此可以多次发出完整的 TestTasks 集。

当最后一个 TestTask 倒计时到零时,它会发出 Event 信号,然后 PoolTest 将再次运行,向 GUI 发出整个测试运行完成的信号(不必费心列出 GUI 的东西'因为不相关)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;


namespace WindowsFormsApplication1
{

public class TestTask: Task{
public int validated;
public int fromVal, toVal;
public int ticks;

    long collatz(long n)
    {
        while (n != 1)
        {
            if (n % 2 == 0)
                n = (n / 2);
            else
                n = (3 * n + 1);
        }
        return (n);
    }

    public override void run(){
        int index;
        int localTo = toVal;
        int localFrom = fromVal;
        int localVal = 0;
        for (index = localFrom; index < localTo; index++)
        {  // if not disproved, inc the stack 'validated'
            if (1 == collatz(index + 1)) localVal++;
        }
        validated = localVal; // put stack result into instance field,
    }
    public TestTask(int paramx, EventHandler OnDone)
        : base(paramx, OnDone){}
};


/* a task that submits testTask instances.
*/
public class PoolTest:Task{
    int FnumTasks;
    int FnumTests;
    int Fcount;
    int FtestCount;
    int taskCount;
    int startTicks;
    int endTicks;
    int totalTicks;
    EventHandler FonTaskComplete;
    AutoResetEvent  testCompleteEvent;
    public int average;
    public int testTicks;
    public int Vone;
    public int Vtot;
    public TestTask thisTestTask;

    public PoolTest(int testsNum, int tasks, int count, EventHandler taskDone,
        EventHandler testDone)
        : base(0, testDone)
    {
        FnumTests=testsNum;
        FtestCount=testsNum;
        FnumTasks=tasks;
        Fcount=count;
        Vtot = 0;
        Vone = 0;
        totalTicks = 0;
        FonTaskComplete=taskDone; // call after each test to report ticks
        testCompleteEvent= new AutoResetEvent(false);
    }
    void submitAtest(){  // queue up numTasks testTask instances
        taskCount=FnumTasks;
        startTicks = System.Environment.TickCount;

//*********************THIS IS WHERE THE RANGES AND TASKS ARE ASSEMBLED

        int startNum = 0;   // here, start at 0 and build up the ranges
        int countIncrement=Fcount/FnumTasks;  // calc. range size
        int endNum=startNum+countIncrement;   // and so init. start/end  
        TestTask newTask;
        for (int i = 1; i < FnumTasks; i++) // one less than requested
        {
            newTask=new TestTask(0, taskComplete);
            newTask.fromVal=startNum;   // load in the start/end for the loop
            newTask.toVal = endNum;
            myPool.submit(newTask);     // off it goes, see you later
            startNum = endNum;          // now move range up for  
            endNum += countIncrement;     // next TestTask
        }
        // treat last range separately to cover div rounding when
        // calculating 'countIncrement'
        newTask = new TestTask(0, taskComplete); // do last one
        newTask.fromVal = startNum;
        newTask.toVal = Fcount;
        myPool.submit(newTask);   // send off the last one
    }

//*****************************************************************

    public override void run(){
        submitAtest(); //start off the first run of testTasks
        testCompleteEvent.WaitOne();
    }
    void taskComplete(object sender, EventArgs e){  // called when a testTask
        bool finishedTasks;                         // instance is complete
         lock (this)
        {
            thisTestTask = (TestTask)sender;
            taskCount--;                            // another one down
            Vone += thisTestTask.validated;         // Vone - total for one run
            Vtot += thisTestTask.validated;         // total for all runs
            finishedTasks = (taskCount == 0);       // this run all done yet?
            if (finishedTasks)
            {
                endTicks = System.Environment.TickCount; // yes, so calc. elapsed time
                testTicks=endTicks-startTicks;
                thisTestTask.ticks = testTicks;
                totalTicks=totalTicks+testTicks;
                if (0!=--FtestCount) {                   // done all the test runs?
                    thisTestTask.validated = Vone;       // use this field to return run total
                    FonTaskComplete(thisTestTask, null); // and signal result of test
                    Vone = 0;
                    submitAtest();                      // no, so send off another load
                }
                else{
                        average=totalTicks/FnumTests;     // done all test runs!
                        testCompleteEvent.Set();          // signal all runs completed
                    };
            }
        }
    }
};
}
于 2012-04-16T04:08:39.510 回答