2

pthread プログラミングについて、まだよくわからないことがあります。そして、誰かが絶対的な答えを教えてくれれば幸いです。

以前の質問は次 のとおりです。単純な Pthread プログラミングで配列変数を割り当てるにはどうすればよいですか?

そして今、私は行列の乗算に取り組んでいます。これを使用するとうまく機能します:

typedef struct {
    int rowIdx;
    int (*matA)[SIZE], (*matB)[SIZE], (*matC)[SIZE];
} newType; 

int main (){
    int matriksA[SIZE][SIZE];
    int matriksB[SIZE][SIZE];
    int matriksC[SIZE][SIZE];

    for (i=0;i<NUM_THREAD;i++) {
         (*block).rowIdx = i;
         (*block).matA = matriksA;
         (*block).matB = matriksB;
         (*block).matC = matriksC;
         pthread_create(&arrThread[i], NULL, matrixMul, (void *)block);
         block++;
    }
}

void *matrixMul(void *x){
    newType *p = (newType *) x;
    int i = (*p).rowIdx;
    int j,k;
    for (j=0;j<SIZE;j++){
        int result = 0;
        for(k=0;k<SIZE;k++){
            int MAik = (*p).matA[i][k];
            int MBkj = (*p).matB[k][j];
            result = result + (MAik*MBkj);
        }
        (*p).matC[i][j] = result;
    }
    pthread_exit(NULL);
}

matrixMul は行列乗算 matC = matA x matB を実行しています。

以前にこの構造体を使用しようとしましたが、うまくいきませんでした。

typedef struct {
    int rowIdx;
    int **matA, **matB, **matC;
} newType; 

どうやら私が読んだことから、変数配列は、配列の最初の要素のアドレスを保持するポインターと見なすことができます。2 次元配列に関しては、コンパイラに列のサイズを伝える必要があります。したがって、typedef 構造体では **matA の代わりに (*matA)[SIZE] を使用する必要があります。

しかし、私はそこで何をしていたのかまだわかりません。ポインターを割り当てて 2D 配列を別の 2D 配列にコピーしただけですか? ちょっと紛らわしい....笑...

次の質問は、これらの行に関するものです。

(*block).matA = matriksA;
(*block).matB = matriksB;
(*block).matC = matriksC;
  1. そこで実際に何が起こったのですか?上記のコードの各ブロック変数には、行列データの独自のコピーがありますか? それとも、ポインターがメモリ内の同じ場所を参照するようにして共有するだけですか?つまり、matA、matB、および matC は (オブジェクト指向プログラミングのように) 静的変数のように動作しますか? つまり、matA、matB、および matC のコピーは 1 つしかありません。スレッドは共有データに同時にアクセスしますか? または、「matA」のコピーが多数あり、それぞれが RAM に独自の異なる割り当てを持っていますか?

  2. 私の最初の投稿と同じ質問ですが、これらの行の背後で何が起こったのですか? (*z).arrA = arrayA; (*z).arrB = arrayB; (*z).arrC = arrayC;

  3. 上記のコードは、タスク (配列の加算と行列の乗算) を実行するのに十分効率的ですか? または、メモリ割り当ての観点からより効率的な別の方法はありますか?

@Code-Guru: 新しい質問を投稿しました。

4

1 に答える 1

3

スレッド化プログラミングの基本的な問題は、2 つの別々のスレッドが同時にデータを変更しようとしたり、別のスレッドが変更している可能性のあるデータを読み取ったりする可能性がないことを確認することです。(データが変更される危険がない場合は、2 つのスレッドが同じ変更されていないデータを同時に読み取ってもまったく問題ありません。)

行列の乗算の問題に適用すると、スレッドは行列 A と行列 B のみを読み取るため、これらの変数へのアクセスを制御する必要はありません — スレッドを起動する前に初期化されていると仮定します。

一方、結果の Matrix C は書き込みのためにアクセスされるため、ワークロードを分割して、2 つのスレッドが同じ要素にアクセスしないようにする必要があります (これらのスレッドは、Matrix のばらばらのサブセットで作業しています)。 C) または、特定の時間に 1 つのスレッドのみが特定のセルを変更するようにアクセスを調整する必要があり、これを相互排除 (a mutex) または同等のもので強制しました。

あなたの質問

blockとりわけ、 がどのように定義されているかを示していません。以下に示すような SSCCE ( Short, Self-Contained, Correct Example ) を示していただけると助かります。これにより、コード フラグメントをリバース エンジニアリングして動作するコードにする必要がなくなります。適切に行うと、多くのスペースを占有しません。(コードが完全ではなかったため、この回答の以前の版は接線で外れました!)

オリジナルでは、x行列NUM_THREADを処理するスレッドを作成しました。またはの定義を示していないため、2 つのサイズが等しいと仮定する必要があります。2 つの定数の相対的なサイズに応じて、さまざまな災害のレシピが利用可能でした。SIZESIZESIZENUM_THREAD

  1. スレッドにはすべて、作業する同じマトリックスが与えられています。これは、あなたが本当に求めていたものです。各スレッドには、同じメモリへのポインタがあります。

  2. (*z).arrA = arrayA;参照する が割り当てであると仮定すると、(*block).arrA = matriksA;に SIZE 整数の配列へのポインターを割り当てていますblock->arrA(これは と同等です(*block).arrA)。それは少しゆがんでいますが、正当です。その使い方には注意が必要です。

  3. コードが十分に効率的かどうかを尋ねます。最初のサブ質問: 正しい答えが得られますか (それが保証されますか)? それについてはまだよくわかりません。ただし、各スレッドが結果マトリックスの 1 つの列で作業している場合は、十分に安全です。

SSCCE

このコードは C99 構造を使用しています。C89 ではコンパイルできません。

#include <stdio.h>
#include <pthread.h>

enum { SIZE = 3 };

typedef struct
{
    int   rowIdx;
    int (*matA)[SIZE];
    int (*matB)[SIZE];
    int (*matC)[SIZE];
} newType; 

extern void *matrixMul(void *);

static void print_matrix(const char *tag, int d1, int d2, int matrix[d1][d2])
{
    printf("%s: (%d x %d)\n", tag, d1, d2);
    for (int i = 0; i < d1; i++)
    {
        printf("%d:", i);
        for (int j = 0; j < d2; j++)
            printf(" %6d", matrix[i][j]);
        putchar('\n');
    }
}

int main(void)
{
    int matriksA[SIZE][SIZE] = { {  1,  2,  3 }, {  4,  5,  6 }, {  7,  8,  9 } };
    int matriksB[SIZE][SIZE] = { { 11, 12, 13 }, { 14, 15, 16 }, { 17, 18, 19 } };
    int matriksC[SIZE][SIZE];
    newType  thedata[SIZE];
    newType *block = thedata;
    pthread_t arrThread[SIZE];

    for (int i = 0; i < SIZE; i++)
    {
         block->rowIdx = i;
         block->matA = matriksA;
         block->matB = matriksB;
         block->matC = matriksC;
         //matrixMul(block);
         pthread_create(&arrThread[i], NULL, matrixMul, block);
         block++;
    }

    for (int i = 0; i < SIZE; i++)
        pthread_join(arrThread[i], 0);

    print_matrix("Matrix A", SIZE, SIZE, matriksA);
    print_matrix("Matrix B", SIZE, SIZE, matriksB);
    print_matrix("Matrix C", SIZE, SIZE, matriksC);
}

void *matrixMul(void *x){
    newType *p = (newType *) x;
    int i = p->rowIdx;
    for (int j = 0; j < SIZE; j++)
    {
        int result = 0;
        for(int k = 0; k < SIZE; k++)
        {
            int MAik = p->matA[i][k];
            int MBkj = p->matB[k][j];
            result += MAik * MBkj;
        }
        p->matC[i][j] = result;
    }
    //pthread_exit(NULL);
    return(0);
}

マトリックス印刷機能を追加した (そしてそれを使用した) ことにお気付きかもしれません。3x3 行列のペアのサンプル データも追加し、答えが正しいことを確認しました。私は2つのステップでテストを行いました:

  1. コードのシングル スレッド バージョンが正しい答えを生成したことを確認します。
  2. スレッドを追加します。

ステップ 1 で間違った答えが得られた場合、修正する必要があるのは基本的な計算だけです。これはスレッドに起因する問題ではありません (スレッドが 1 つしかないためです!)。幸いなことに、正しい答えが得られます。スレッドの追加は簡単でした。

出力

$ ./ta
Matrix A: (3 x 3)
0:      1      2      3
1:      4      5      6
2:      7      8      9
Matrix B: (3 x 3)
0:     11     12     13
1:     14     15     16
2:     17     18     19
Matrix C: (3 x 3)
0:     90     96    102
1:    216    231    246
2:    342    366    390
$ 
于 2013-03-25T01:59:00.663 に答える