2012-01-30 3 views
2

ずに私が持っているのstd ::文字列を使用して、正しいベース名のFreeBSDの作業にではなく、

まず

// compile with -lpthread 
// TEST: 
// basename 


#include <pthread.h> 
#include <string.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <libgen.h> 
#include <limits.h> 
#include <inttypes.h> 


// DATASET_LEN 
#ifndef DATASET_LEN 
#define DATASET_LEN 10000 
#endif 
// THREADS_NUM 
#ifndef THREADS_NUM 
#define THREADS_NUM 16 
#endif 


// need to call free(3) after 
char** generateArray() { 
    char** dataset = (char**)malloc(sizeof(char*) * DATASET_LEN); 
    // fill dataset 
    for (size_t i = 0; i < DATASET_LEN; ++i) { 
     dataset[i] = (char*)malloc(sizeof(char) * CHAR_MAX); 
     sprintf(dataset[i], "%i/%i/", rand(), rand()); 
    } 

    return dataset; 
} 

// pthread_create(3) callback 
void* run(void* args) { 
    char** dataset = generateArray(); 
    char* baseName; 

    for (size_t i = 0; i < DATASET_LEN; ++i) { 
     baseName = basename(dataset[i]); 
     printf("%s\n", baseName); 

     free(dataset[i]); 
    } 

    free(dataset); 
} 

// main 
int main(int argc, char** argv) { 
    pthread_t* threads = (pthread_t*)malloc(sizeof(pthread_t) * THREADS_NUM); 
    // threads start 
    for (int i = 1; i <= THREADS_NUM; ++i) { 
     pthread_create(&threads[i-1], NULL, run, NULL); 
     fprintf(stderr, "Thread %u started\n", i); 
    } 
    // threads join 
    for (int i = 1; i <= THREADS_NUM; ++i) { 
     pthread_join(threads[i-1], NULL); 
     fprintf(stderr, "Thread %u finished\n", i); 
    } 
    free(threads); 

    return EXIT_SUCCESS; 
} 

第二:

// compile with -lpthread 
// TEST: 
// basename 


#include <pthread.h> 
#include <string.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <libgen.h> 
#include <limits.h> 
#include <inttypes.h> 
#include <string> 


// DATASET_LEN 
#ifndef DATASET_LEN 
#define DATASET_LEN 10000 
#endif 
// THREADS_NUM 
#ifndef THREADS_NUM 
#define THREADS_NUM 16 
#endif 


// need to call free(3) after 
char** generateArray() { 
    char** dataset = (char**)malloc(sizeof(char*) * DATASET_LEN); 
    // fill dataset 
    for (size_t i = 0; i < DATASET_LEN; ++i) { 
     dataset[i] = (char*)malloc(sizeof(char) * CHAR_MAX); 
     sprintf(dataset[i], "%i/%i/", rand(), rand()); 
    } 

    return dataset; 
} 

// pthread_create(3) callback 
void* run(void* args) { 
    char** dataset = generateArray(); 
    char* baseName; 
    std::string tmpStr; 

    for (size_t i = 0; i < DATASET_LEN; ++i) { 
     baseName = basename(dataset[i]); 
     tmpStr = std::string(baseName); 
     printf("%s\n", tmpStr.c_str()); 

     free(dataset[i]); 
    } 

    free(dataset); 
} 

// main 
int main(int argc, char** argv) { 
    pthread_t* threads = (pthread_t*)malloc(sizeof(pthread_t) * THREADS_NUM); 
    // threads start 
    for (int i = 1; i <= THREADS_NUM; ++i) { 
     pthread_create(&threads[i-1], NULL, run, NULL); 
     fprintf(stderr, "Thread %u started\n", i); 
    } 
    // threads join 
    for (int i = 1; i <= THREADS_NUM; ++i) { 
     pthread_join(threads[i-1], NULL); 
     fprintf(stderr, "Thread %u finished\n", i); 
    } 
    free(threads); 

    return EXIT_SUCCESS; 
} 

両方のプログラムはLinuxで通常動作しますが、しかしfreebsdで最初に(std :: stringなしで)動作しません
誰でも説明できますか?

私は/usr/src/lib/libc/gen/basename.cでfreebsd srcを見て、静的なvar in関数を見てください。
しかし、それのために、STD ::文字列のプログラムでも必要がない私が意味する、通常では

通常動作しますが、それは出力のみの数字、そして新しいラインのテストのために

は、私が使用します。 ./freebsd-threaded-basename | egrep -av '^[0-9\n\s]+$' | env LANG=c less

UPD私は(のstrdupを使用しようとする)やstrcpyの()結果は同じである - 正常でない
UPD * すべて *時間のstd ::文字列とバージョンが実行されますLinux manual page on pthreadsから

+0

小さな点: 'std :: string'のバージョンで' tmpStr'に直接割り当てるのはなぜですか? 'tmpStr = basename(dataset [i]);' –

+0

のように私はこの小さなテストプログラムを速く書いたので、私はちょうどあなたのアドバイス、結果は同じであることをこのコードでテストします – azat

答えて

2

あなたのプログラムが予期しない動作をする理由は、スレッドセーフではありませんされ、basenameです。 basenameは少し古いです。現代のC++アプリケーションは、ファイルパスを解析するために他の手段を使用する傾向があります。 Boost Filesystem Libraryは普及しており、これを使用することができます。

あなたがbasenameを使う、という場合は、(それがprintf、またはstrcpy、または他のいくつかのこと)basenameの結果を得るであろういくつかのコードと一緒にクリティカルセクションに配置します。これにより、basenameの結果が複数のスレッドから同時にアクセスされないことが保証されます。正しい行動を意味します。今

「なぜ」についていくつかの当て推量。 (推測することはできないので、スレッドセーフではないマルチスレッドプログラムがどのくらい正確に動作するかは推測できません)。

部分逐次プログラムの最初のバージョン、部分的に並列にbasenameループを実行する(basename関数とループ自体)、(printf及びfreeは、スレッドセーフ機能であり、その実装は、クリティカルセクションによって保護されています)。

セカンドバージョンは、より多くのシーケンシャルなコードを意味し、std::stringを追加します。新しい文字列にメモリを割り当て、古いメモリを解放します(これらの操作はどちらもスレッドセーフであり、クリティカルセクションで保護されています)。また、(一部の実装では)共有カウンタを更新するためにアトミック操作を使用しており、並列性も低下します。このすべてが実際にプログラムを並列から完全に順次変換します。すべてのスレッドは主にいくつかのミューテックスを待っています。あるいは、時には複雑なprintf/memory/std :: string計算を実行することもあります。そして非常にまれに、スレッドのうちの1つが比較的簡単にbasenameの計算を行います。ほぼbasenameの周りにクリティカルセクションを追加したかのように。

おそらく、printffreeは、この場合ほとんどの場合、プログラムを順番に実行できるので、Linuxテストの正しい結果が得られます。 (なぜなら、何かがLinuxでは異なったハードウェアによって行われるからです)。

+0

'std :: string'コンストラクタはスレッドセーフであるため、いくつかのmutexを待って、このデータのために、通常は読み取り/書き込みが行われますか?しかし、変数を 'std :: string'コンストラクタに渡すと、このコードはスレッドセーフではありませんか? – azat

+0

いいえ、これはスレッドセーフではありません。すべてのスレッドがmutexで同期するのがビジーであるため、すべてが正常に見えます。だから、いつも 'std :: string'でも問題がある機会があります。また、あるスレッドが他のスレッドで生成された番号を印刷する可能性があります(これは乱数では分かりません)。 –

+0

私はこれを感謝します、ありがとう。 – azat

1

を期待どおりに動作します:

POSIX.1-2001とPOSIX.1-2008が標準で指定されたすべての機能は、次の機能を除いて、スレッドセーフでなければならないことを要求:

[機能の一覧]

basename() 

のでbasenameがそのようにかかわらず、(スレッドセーフであることが保証されていません私の実装はそれを行うかもしれない)。アプリケーションを移植可能にするには、mutexのようなものでコールを保護する必要があります。

また、それが明示的に述べていPOSIX参照参照:

ベース名()関数は、文字列はパスによって指さ変更することができる、及びその後ので上書きすることができる静的ストレージへのポインタを返すことができるのbasename()を呼び出します。

basename()関数はスレッドセーフである必要はありません。特に

http://www.freebsd.org/cgi/man.cgi?query=basename&sektion=3

を:あなたがここで見つけることができますFreeBSDでは、上の

+0

私はこれを理解しています。しかし、なぜstd :: stringでは正常に動作しますか? – azat

+0

@azat 'std :: string'を使ったバージョンが実行されるたびに、期待通りに動作しますが、他のバージョンでは動作しません。すべての時間またはほとんどの時間? –

+0

もちろん、すべての時間。 – azat

1

これは、(ベース名のマニュアルページで説明されている)

実装に関する注 ベース名()関数は、OVEであろう最初の呼び出しにcated 同種内部記憶空間へのポインタを返しますその後の呼び出しによって書き換えられます。したがって、スレッド化されたアプリケーションの場合は basename_r()が優先されます。

したがって、basename()から返されるデータは、使用している他のスレッドによって上書きされている可能性があります。 basename_rを使用すると、これを防ぐことができます。

+2

2番目のプログラムで動作する理由は、純粋な運(といくつかのタイミング)のように見えると付け加えておきます。 std :: stringはコンストラクタ内の文字列のコピーを作成します。これは、basename()を呼び出した後にスレッドが最初に行うことであるため、文字列を 'すぐに'読み込みます。 std :: stringのない実装では、basename()呼び出しの後にprintfに入ります。これは可変引数を処理し、実際に指定された文字列で何かを行う前にフォーマット文字列を解析する必要があります。その時までに上書きされた可能性は非常に高いです。 – AVL

+0

いいえ、それはコンストラクタで純粋な運や対処文字列ではありません。私は 'strdup()'や 'strcpy()'を使用しようとしましたが、結果は同じです - 普通ではない – azat

関連する問題