2

私はこのコードを書きました

#include <stdio.h>      /* Input/Output */
#include <stdlib.h>     /* General Utilities */
#include <pthread.h>    /* POSIX Threads */
unsigned int cnt=0;  /*Count  variable%*/
const int NITERS=1000;
void count()
{
    int i=0;
    for(i=0; i<NITERS; i++)
    {
        cnt++;
    }
    pthread_exit(0);
}
int main()
{
    pthread_t tid1,tid2;
     /* create threads 1 and 2 */  
    pthread_create(&tid1,NULL,count,NULL);
    pthread_create(&tid2,NULL,count,NULL);
    /* Main block now waits for both threads to terminate, before it exits
       If main block exits, both threads exit, even if the threads have not
       finished their work */ 
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    if(cnt!=(unsigned)NITERS*2)
    {
        printf("BOOM! cnt=%d, it should be %d\n",cnt,NITERS*2);
    }
    else
    {
        printf("OK! cnt=%d\n",cnt);
    }
    exit(0);
}

そしてそれはこの結果を示しています。 コードへの結果

cnt が 2000 になることもありますが、ほとんどの場合、結果は 2000 未満になります。なぜそうなるのか、またはその背後にある理由を説明していただけますか? それを修正する方法。あなたの答えと理由はきっと大きな助けになるでしょう。

4

4 に答える 4

3

unsigned int cnt=0;スレッド間で共有可能であり、操作++はアトミックに増加しませんcnt。2 つのスレッドが の同じ値を読み取り、cntを増やし、 を上書きする場合がありますcnt。セマフォやミューテックスなどの同時実行制御メカニズムを適用する必要があります。


次のコマンドを使用してコードを逆アセンブルする場合 (コード名が であるとしますthread1.c)

~$ gcc thread.c -lpthread -S  

出力アセンブリ コード名はthread1.s.

あなたの意志cnt++は、コード内の低レベルの複数の命令です:

    movl    $0, -12(%ebp)
    jmp .L2
.L3:
    movl    cnt, %eax
    addl    $1, %eax
    movl    %eax, cnt
    addl    $1, -12(%ebp)
.L2:
    movl    NITERS, %eax

(1)cnt最初に (2) に移動し、次に (3)%eax
に 1 を追加して後ろに移動します。%exc
%eaxcnt

また、この行の間のスレッド コンテキストの切り替えにより、同じ値がcnt複数のスレッドによって読み取られます。したがってcnt++、アトミックではありません。

注:グローバル変数は のようにスレッド共有可能であり、で宣言したcntようなローカル変数はスレッド固有です。 icount()


私はあなたのコードを修正し、セマフォを使用して同時実行制御を課しましたが、今では正常に動作します。

変更されたコードのみが表示されます

#include <pthread.h>    /* POSIX Threads */
#include <semaphore.h>
unsigned int cnt=0;  /*Count  variable%*/
const int NITERS=1000;

sem_t mysem;

void count()
{
    int i=0;
    for(i=0; i<NITERS; i++)
    {
        sem_wait(&mysem);
        cnt++;
        sem_post(&mysem);
    }
    pthread_exit(0);
}
int main()
{
    if ( sem_init(&mysem,0,1) ) {
     perror("init");
    }
    // rest of your code 
} 

これはうまくいきます!いくつかの例:

nms@NMS:~$ ./thread 
OK! cnt=2000
nms@NMS:~$ ./thread 
OK! cnt=2000
nms@NMS:~$ ./thread 
OK! cnt=2000
于 2012-12-22T17:35:31.147 に答える
1

インクリメント演算子は通常、非アトミックな read-modify-write によって実装されます。

スレッド間の非アトミック読み取り-変更-書き込みは、時々これを行うことができます:

Thread 1:    Thread 2:     count
Read count   ...           1
Add 1        Read count    1
Write count  Add 1         2 
...          Write count   2

その結果、カウントが予想よりも少なくなりました。

複数のスレッドにまたがる共有リソースにアクセスする場合は、ミューテックスなど、何らかのスレッド対応のロック メカニズムで保護する必要があります。

于 2012-12-22T17:38:19.030 に答える
1

2 つのスレッドが保護なしで共有リソースにアクセスするため、競合状態が適用されます。増加操作はアトミックではないため、実際にはマシン操作に関して次のようなことができます。

Thread 1                   Thread 2
Load value of cnt        
                           Load value of cnt
Increase value of cnt      
Write value of cnt         Increase value of cnt
                           Write value of cnt

両方のスレッドが を増やしましたがcnt、実際には 1 だけ増えたことに注意してください。結果を決定論的にしたい場合は、共有リソース ( cnt) を保護する必要があります。たとえば、アクセスする前にロックしてください。

于 2012-12-22T17:38:46.580 に答える
1

競合状態の問題があります。詳細 (Visual Basic について話していることは知っていますが、これらのことは飛ばしてください) here .
それを解決するには、ミューテックスが必要です。それをグローバル変数として宣言します。

pthread_mutex_t mux;  

それを初期化します。

pthread_mutex_init(&mux,NULL);

次に、共有変数の読み取りに使用します。

void count()
{
    int i=0;
    for(i=0; i<NITERS; i++)
    {
        pthread_mutex_lock(&mux);
        cnt++;
        pthread_mutex_unlock(&mux);
    }
    pthread_exit(0);
}

これはすべて、同じ変数をインクリメントする2つのスレッドがあるためです。それらは変数をインクリメントする前にフェッチし、レジスタに入れます。したがって、読み取り値は一意ではありません。すべてのスレッドには独自のコピーがあるため、各スレッドは無視する可能性がありますメモリ内のアドレスに効果的に書き込むまで、他の変更。
NB : スレッドが順序どおりに変数を更新するようにする場合 (つまり、スレッド 2 がカウントを開始したときに、スレッド 1 が中断されることなく NITERS までカウントする)、for の前にミューテックスをロックする必要があります。

于 2012-12-22T17:45:24.470 に答える