0

そのため、多数の数値セットに対してコラッツ予想を検証し、検証された数値の合計を返す単純なマルチスレッド プログラムを作成しようとしています。各スレッド (合計 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) で動かなくなっていると推測しています。long int を int に置き換えてみましたが、それでもオーバーフローに悩まされているようです。助言がありますか?そして多大な助けをありがとう。

最終編集: よし、int の代わりに long long を使用したところ、プログラムは問題なく動作するようになりました。みんな助けてくれてありがとう!

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に標準演算子の代わりに関数を使用することです。++もう1つのアプローチは、共有変数にアクセスするときにスピンロックやミューテックスなどの同期オブジェクトを使用することですが、インクリメント操作を同期する必要がある場合は、それはやり過ぎです。

詳細が必要な場合は、collatz機能コードを入力してください。

'usr'が示唆しているように、パフォーマンスを向上させるために、スレッドごとに個別の変数を使用して、メインスレッドでそれらを合計することができます。このシナリオでは、誤った共有を避けるために、同じキャッシュラインを共有しないようにこれらの変数を埋める必要があります。

collatz_thread一貫性のない結果のもう1つの原因となる可能性がある、機能を提供していません。その理由は、新しいスレッドを作成する呼び出し間で変更されるスレッド#を格納する変数( )へのポインターを渡すためです。そのため、OSのスケジューラーの状態によっては、変数が既に変更されている&thread間、新しいスレッドが開始されない場合があります。thread別の値なので、同じデータセットを実行するスレッドが複数あり、他のセットはスキップされる可能性があります。動作はスレッドスケジューラの現在の状態に依存するため、ほとんど予測できません。

ソリューションは、アドレスを渡す代わりにthread変数をキャストしてから、関数内で次の場所にキャストし直します。void*collatz_threadint

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

私はこれを試してみて、いくつかの良い、あまりにも良い結果を得ています:((どこかで何かが間違っていますが、私はそれを見ていません.1からnまでのnの場合でも、時間のオーダーでランタイムを取得していません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 スレッドと表示されていますが、これはプール全体に対するものであることに注意してください。他のタスクが完了するのを待って常に 1 つのスレッドが使い果たされるため、検証を実行するために実際に使用できるスレッドは 13 だけでした。なぜか最適でした。

OK、私の i7 はすべての 4/8 コアでフラットアウトしていますが、より多くのコアがあり、それらすべてに作業を分割したという理由だけで、数時間の実行時間が数秒に短縮されることはありません :(

これは私が使用したものです。ほとんどのツール/コードが転がっていたので、あなたのやり方とは少し異なります。そもそも、Visual C++ です。2つのクラスがあります。各実行は、複数の「TestTask」インスタンスをスレッドプールに送信する「PoolTest」クラスによって管理されます。検証される整数の全範囲のセグメントごとに 1 つです。コードがコピーされ、TestTask クラスに貼り付けられていることがわかります。PoolTest コードで TestTask がアセンブルされている場所を強調しました。次に、'PoolTest' クラスは、すべての 'TestTask' インスタンスが完了するのをイベントで待機します。完了時に TestTask が PoolTest 'taskComplete' メソッドにコールバックするため、これを行うことができます。このメソッドは、TestTask がサブミットされるとカウントアップされるスレッドセーフ カウンターにアクセスし、'

私が再利用したこのコードは、実行を複数回繰り返して平均時間を取得できるため、少し複雑になります。そのため、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 に答える