简单的多线程帮助? C ++,WaitForSingleObject和Synchronization

时间:2012-04-15 17:42:23

标签: c++ multithreading visual-studio-2010 synchronization

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

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

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

编辑:这是我更新的主要功能和Collat​​z功能。我修改了它,以便每个线程访问一个单独的变量以避免同步问题,但问题仍然存在。打印出“已验证”*

时,没有一致的值

再次编辑:好吧,所以我删除了每个MladenJanković的“thread”int,并且只使用一个简单的计数器将不同的间隔分配给创建的线程。现在有一个一致的,正确的“验证”值。但是,当有1,000,000个数字时,我仍然无法让程序真正完成。对100,000或甚至10,000的测试工作完美无瑕,但当我将其增加到1,000,000个数字时,程序无限期地(小时)运行而不实际返回值。我猜它正陷入特定的价值(例如马丁詹姆斯指出的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;
}

}

3 个答案:

答案 0 :(得分:1)

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

如果您想了解更多详情,请提供collatz功能代码。

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

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

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

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

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

答案 1 :(得分:1)

试着看看这个:

Semaphore and threads explenation from MSDN

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

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

此外,在你的线程对象中,一旦你完成共享变量的工作,你需要释放信号量,以便其他线程可以锁定该特定的信号量。

尝试查看我给你的链接上的示例,我相信你会让它工作得很好。

答案 2 :(得分: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类中。我在PoolTest代码中突出显示了TestTask的组装位置。然后'PoolTest'类等待一个Event来完成所有'TestTask'实例 - 它可以这样做,因为TestTask在完成时回调到PoolTest'taskComplete'方法。此方法访问一个线程安全的计数器,该计数器在提交TestTasks时计数,并通过'taskComplete'方法计数。

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

当最后一次TestTask倒计时到零时,它会发出事件信号,然后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
                    };
            }
        }
    }
};
}