1

同期の理由を突き止めようとして、ひどい時間を過ごしています。pthread ライブラリを使用すると、コードがデッドロックします。winapi プリミティブを使用すると、pthread の代わりに問題なく動作します。c++11 スレッドの使用も問題なく動作します (Visual Studio 2012 サービス パック 3 でコンパイルされていない限り、クラッシュするだけです - Microsoft はそれをバグとして受け入れました)。別のOSを試す機会がありませんでした。

問題を説明する簡単なプログラムを作成しました。コードはデッドロックを示しているだけです。設計がかなり貧弱であり、より適切に記述できることは十分承知しています。

typedef struct _pthread_event
{
     pthread_mutex_t Mutex;
     pthread_cond_t Condition;
     unsigned char  State;
} pthread_event;

void pthread_event_create( pthread_event * ev , unsigned char init_state )
{ 
    pthread_mutex_init( &ev->Mutex , 0 );
    pthread_cond_init( &ev->Condition , 0 );
    ev->State = init_state;
}

void pthread_event_destroy( pthread_event * ev )
{
    pthread_cond_destroy( &ev->Condition );
    pthread_mutex_destroy( &ev->Mutex );
}

void pthread_event_set( pthread_event * ev , unsigned char state )
{
    pthread_mutex_lock( &ev->Mutex );
    ev->State = state;
    pthread_mutex_unlock( &ev->Mutex );
    pthread_cond_broadcast( &ev->Condition );
}

unsigned char pthread_event_get( pthread_event * ev )
{
    unsigned char result;
    pthread_mutex_lock( &ev->Mutex );
    result = ev->State;
    pthread_mutex_unlock( &ev->Mutex );
    return result;
}

unsigned char pthread_event_wait( pthread_event * ev , unsigned char state , unsigned int timeout_ms )
{
    struct timeval time_now;
    struct timespec timeout_time;
    unsigned char result;

    gettimeofday( &time_now , NULL );
    timeout_time.tv_sec = time_now.tv_sec           + ( timeout_ms / 1000 );
    timeout_time.tv_nsec = time_now.tv_usec * 1000  + ( ( timeout_ms % 1000 ) * 1000000 );

    pthread_mutex_lock( &ev->Mutex );
    while ( ev->State != state ) 
          if ( ETIMEDOUT == pthread_cond_timedwait( &ev->Condition , &ev->Mutex , &timeout_time ) ) break;

    result = ev->State;
    pthread_mutex_unlock( &ev->Mutex );
    return result;
}

static pthread_t        thread_1;
static pthread_t        thread_2;
static pthread_event    data_ready;
static pthread_event    data_needed;

void * thread_fx1( void * c )
{
    for ( ; ; )
    {
        pthread_event_wait( &data_needed , 1 , 90 );
        pthread_event_set( &data_needed , 0 );
        usleep( 100000 );
        pthread_event_set( &data_ready , 1 );
        printf( "t1: tick\n" );
    }
}

void * thread_fx2( void * c )
{
    for ( ; ; )
    {
        pthread_event_wait( &data_ready , 1 , 50 );
        pthread_event_set( &data_ready , 0 );
        pthread_event_set( &data_needed , 1 );
        usleep( 100000 );
        printf( "t2: tick\n" );
    }
}


int main( int argc , char * argv[] )
{
    pthread_event_create( &data_ready , 0 );
    pthread_event_create( &data_needed , 0 );

    pthread_create( &thread_1 , NULL , thread_fx1 , 0 );
    pthread_create( &thread_2 , NULL , thread_fx2 , 0 );

    pthread_join( thread_1 , NULL );
    pthread_join( thread_2 , NULL );

    pthread_event_destroy( &data_ready );
    pthread_event_destroy( &data_needed );

    return 0;
}

基本的に、2 つのスレッドが互いにシグナルを発します。短いタイムアウトの後にシグナルが送信されなくても、何かを開始し、独自のことを実行します。

何がうまくいかないのですか?

ありがとう。

4

1 に答える 1

1

問題は へのtimeout_timeパラメータpthread_cond_timedwait()です。それをインクリメントする方法は、最終的にはすぐに無効な値になり、ナノ秒の部分は10億以上になります。この場合pthread_cond_timedwait()、おそらく で戻りEINVAL、おそらく実際には条件を待つ前です。

問題は非常に迅速に見つけることができますvalgrind --tool=helgrind ./test_prog(すぐに、すでに 10000000 個のエラーを検出し、カウントをあきらめたと言いました):

bash$ gcc -Werror  -Wall -g test.c -o test -lpthread && valgrind --tool=helgrind ./test
==3035== Helgrind, a thread error detector
==3035== Copyright (C) 2007-2012, and GNU GPL'd, by OpenWorks LLP et al.
==3035== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==3035== Command: ./test
==3035== 
t1: tick
t2: tick
t2: tick
t1: tick
t2: tick
t1: tick
t1: tick
t2: tick
t1: tick
t2: tick
t1: tick
==3035== ---Thread-Announcement------------------------------------------
==3035== 
==3035== Thread #2 was created
==3035==    at 0x41843C8: clone (clone.S:110)
==3035== 
==3035== ----------------------------------------------------------------
==3035== 
==3035== Thread #2's call to pthread_cond_timedwait failed
==3035==    with error code 22 (EINVAL: Invalid argument)
==3035==    at 0x402DB03: pthread_cond_timedwait_WRK (hg_intercepts.c:784)
==3035==    by 0x8048910: pthread_event_wait (test.c:65)
==3035==    by 0x8048965: thread_fx1 (test.c:80)
==3035==    by 0x402E437: mythread_wrapper (hg_intercepts.c:219)
==3035==    by 0x407DD77: start_thread (pthread_create.c:311)
==3035==    by 0x41843DD: clone (clone.S:131)
==3035== 
t2: tick
==3035== 
==3035== More than 10000000 total errors detected.  I'm not reporting any more.
==3035== Final error counts will be inaccurate.  Go fix your program!
==3035== Rerun with --error-limit=no to disable this cutoff.  Note
==3035== that errors may occur in your program without prior warning from
==3035== Valgrind, because errors are no longer being displayed.
==3035== 
^C==3035== 
==3035== For counts of detected and suppressed errors, rerun with: -v
==3035== Use --history-level=approx or =none to gain increased speed, at
==3035== the cost of reduced accuracy of conflicting-access information
==3035== ERROR SUMMARY: 10000000 errors from 1 contexts (suppressed: 412 from 109)
Killed

他に 2 つの小さなコメントがあります。

  1. 正確性を向上させるために、pthread_event_set()ミューテックスのロックを解除する前に条件変数のブロードキャストを実行することができます (間違った順序の影響は、基本的にスケジューリングの決定論を破る可能性があります;helgrindこの問題についても文句を言います);
  2. pthread_event_get() のミューテックス ロックを安全に削除して、値を返すことができますev->State。これはアトミック操作である必要があります。
于 2013-08-05T14:03:52.183 に答える