rn_xtcxyczjh-8同時[スレッド4読み書きロックインタフェース]


2015.10.31 xtcxyczjh(システムプログラマー成長計画)-プログラム設計方法を学習します.
前のコードのメモリ漏洩BUGは3.1を参照してください.今回、読者に写した読み書きロックコード保存アドレス:y 15 m 10 d 31(rw_locker.c,rw_locker.h)
1.需要の概要
次のように読み書きロックを実現します.-特定のプラットフォームに依存しません.-いずれの場合も、追加のパフォーマンスオーバーヘッドは発生しません.
2.準備
『xtcxyczjh』P.48-P.51を読みます.
パラレルクエリをサポートしながら、データ構造の変更をシリアル化できるロックを実現できますか?これがいわゆる読み書きロックであり,共有−反発ロックとも呼ばれる.
読み書きロックの基本的な要件は、書き込み時に他のスレッドの読み取りまたは書き込みを許可しないことです.読み取り時に他のスレッドの読み取りを許可しますが、他のスレッドの書き込みを許可しません.したがって、実装時には、書くときは必ずロックをかけ、最初の読み取りスレッドはロックをかけ、後の他のスレッドが読むときは、ロックの参照カウントを増やすだけです.
3.実現
(1)読み書きロックインタフェースを規定する
準備した内容に基づいて、rw_を新規作成locker.cファイルは、Lockerインタフェースに基づいて読み書きロックを記述するデータ構造を実現する.
/* rw_locker.c */
/*              */

#include "rw_locker.h"
#include "tk.h"
#include "rw_locker.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

typedef enum    _RwLockerModeT {
         RW_LOCKER_NONE,
    RW_LOCKER_WR,
    RW_LOCKER_RD
}RwLockerModeT;

struct _RwLockerT {
    int             readers;    //           
    Locker          *wr_locker; // /     
    Locker          *rd_locker; //           
    RwLockerModeT   mode;       // /      ,            
};

新しいrw_locker.hファイルは、読み書きロックのインタフェースを規定/宣言する.
/* rw_locker.h */
/*        Locker,   rw_locker.c        */

#ifndef RW_LOCKER_H
#define RW_LOCKER_H

#include "locker.h"

struct _RwLockerT;
typedef struct _RwLockerT RwLockerT;

//     
RwLockerT   *create_rw_locker(Locker *wr_locker, Locker *rd_locker);
RetT    lock_wr_locker(RwLockerT *rw_locker);
RetT    lock_rd_locker(RwLockerT *rw_locker);
RetT    unlock_rw_locker(RwLockerT *rw_locker);
void    free_rw_locker(RwLockerT *rw_locker);

#endif

以前のバージョンでは、動的に割り当てられたスレッドロックは解放されず、メモリが漏洩したBUGがあった(修復を急ぐ必要はなかった.このBUGにはユーザーコード部分があり、ユーザーコードは最終的に削除されるため).また、ユーザーにロックを破棄するコールバック関数も提供されなかった.
Lockerインタフェースを変更し、ロックを破棄するインタフェースを追加します.
/* locker.h */
/*                */
//......
typedef void    (*LockerDestroyFuncT)(Locker *locker);
//   
struct _Locker {
    LockerLockFuncT     lock;
    LockerUnlockFuncT   unlock;
    LockerDestroyFuncT  destroy;
    char                dcrt[0];
};
//......

(2)実装インタフェース
[1]読み書きロックの作成
/* rw_locker.c */
/*              */
#include "rw_locker.h"
#include <stdio.h>

//……
RwLockerT   *create_rw_locker(Locker *wr_locker, Locker *rd_locker)
{
    RwLockerT   *rw_locker = NULL;
    return_val_if_p_invalid(NULL != wr_locker && NULL != rd_locker, NULL);

    rw_locker   = (RwLockerT *)malloc(sizeof(RwLockerT));
    if (NULL != rw_locker) {
        rw_locker->readers      = 0;
        rw_locker->mode         = RW_LOCKER_NONE;
        rw_locker->wr_locker    = wr_locker;
        rw_locker->rd_locker    = rd_locker;
    }

    return rw_locker;
}

読み書きロックをスタックに作成し、作成した初期化操作を行います.
[2]書き込みスレッドのロック
/* rw_locker.c */
/*              */
//……
RetT    lock_wr_locker(RwLockerT *rw_locker)
{
    RetT    ret = RET_OK;
    return_val_if_p_invalid(NULL != rw_locker, RET_PARAMETER_INVALID);

    if ((ret = rw_locker->wr_locker->lock(rw_locker->wr_locker) == RET_OK)) {
        rw_locker->mode = RW_LOCKER_WR;
    }

    return ret;
}

ロックロックロックロックロックは、ロックコールバック関数(インタフェース)のタイプを指定します.【locker.hのLockerLockFuncTタイプ.linuxシステムのスケジューリングに基づいて実装されるユーザロック関数はcpthread.cのlock_nest_thread関数である】ユーザは、このインタフェースに従って特定のプラットフォームのロック関数を実装し、特定のロックに値を割り当て、割り当てられたLockerロックをパラメータとして読み書きロックを作成する関数create_rw_lockerに渡す必要がある、書き込み時にロック関数を追加すると、ユーザーが作成したロック関数を使用できます.
リードスレッド操作がある場合、またはライトスレッド操作がロック状態にある場合、ここでの書き込み時にロック文がロックできるまで待機します.
[3]リードスレッドロック
/* rw_locker.c */
/*              */
//……
RetT    lock_rd_locker(RwLockerT *rw_locker)
{
    RetT    ret = RET_OK;
    return_val_if_p_invalid(NULL != rw_locker, RET_PARAMETER_INVALID);

    if ((ret = rw_locker->rd_locker->lock(rw_locker->rd_locker)) == RET_OK) {
        rw_locker->readers++;
        if (rw_locker->readers == 1) {
            ret = rw_locker->wr_locker->lock(rw_locker->wr_locker);
            rw_locker->mode = RW_LOCKER_RD;
        }
        rw_locker->rd_locker->unlock(rw_locker->rd_locker);
    }
    return ret;
}

リード時ロックインタフェースは、ユーザが特定のプラットフォームのロック関数を記述するルートを呼び出すのと同じである.
まず、リードスレッドのロック回数を保護する変数にロックをかけ、リードスレッドのロック回数を増やしてみましょう.この時点で1つのリードスレッドのみがリードする場合、リード・ライト・ロックのリード・ロックは、ライト・スレッドがロックを持っていない場合、最初のリード・スレッド・ロックがロックに成功し、リード・ライト・ロックの状態をリード・モード(rw_locker->mode=RW_LOCKER_RD;)に設定してから、リード・スレッドのロック回数を保護するためにロックを解除します.
[4]書き込み/読み取りスレッドのロック解除
/* rw_locker.c */
/*              */
//……
RetT    unlock_rw_locker(RwLockerT *rw_locker)
{
    RetT    ret = RET_OK;
    return_val_if_p_invalid(NULL != rw_locker, RET_PARAMETER_INVALID);

    if (rw_locker->mode == RW_LOCKER_WR) {
        rw_locker->mode = RW_LOCKER_NONE;
        ret = rw_locker->wr_locker->unlock(rw_locker->wr_locker);
    }else {
        assert(rw_locker->mode == RW_LOCKER_RD);
        if ((ret = rw_locker->rd_locker->lock(rw_locker->rd_locker)) == RET_OK) {
            rw_locker->readers--;
            if (rw_locker->readers == 0) {
                rw_locker->mode = RW_LOCKER_NONE;
                ret = rw_locker->wr_locker->unlock(rw_locker->wr_locker);
            }
            rw_locker->rd_locker->unlock(rw_locker->rd_locker);
        }
    }
    return ret;
}

ロック解除は状態に応じて決定され、保護対象のロックを読み書き直接解除して保護する.ロックを解読するときは、まず参照カウントを保護するロックをロックし、参照カウントを1つ減らします.自分が最後の読み取りである場合、保護対象オブジェクトのロックが解除され、最後に保護参照カウントのロックが解除されます.
[5]リード・ライト・ロックの解除(ロックの破棄)
/* rw_locker.c */
/*              */
//……
void    free_rw_locker(RwLockerT *rw_locker)
{
    if (NULL != rw_locker) {
        rw_locker->rd_locker->destroy(rw_locker->rd_locker);
        rw_locker->wr_locker->destroy(rw_locker->wr_locker);
        rw_locker->rd_locker = rw_locker->wr_locker = NULL;
        free(rw_locker);
    }
}

ユーザーがロックを破棄する関数を呼び出して、ユーザーが作成したロックを破棄します.次に、読み書きロックインタフェースの作成(動的割り当て)で作成した読み書きロックメモリを解放します.
(3)呼び出しインタフェース
以前のコードには、特定のプラットフォームでロックを破棄するコールバック関数は含まれていません(信号変数にも必要ありません).実際には、rw_locker.cとrw_locker.hの2つのファイルは、読み書きロックの1つのインタフェースを実現しています.これらのインタフェースの機能が正しいかどうかを自動的にテストすることで検出できます.インタフェースを呼び出し、インタフェースを実現するために要求されるコールバック関数は、呼び出し者です(このインタフェースを使用する人)のタスクは,インタフェースやコールバック関数というプログラム設計方法を学習する際に,主にインタフェース,コールバック関数の実現メカニズム,およびユーザが特定のプラットフォームで実現したコールバック関数を呼び出す技術ルートを理解することである.
(4)Makefileの修正
主にインタフェースの内容をコンパイルして、コンパイルやリンクに関連するエラーがあるかどうかを確認します.
#Makefile
#make      make all       
all:        main.o dlist.o tk.o autotest.o cpthread.o cfile.o rw_locker.o
    $(CC) $^ $(CFLAGS) $@ $(CFLAG_PTHREAD_LIB)

//......
rw_locker.o: rw_locker.c rw_locker.h
    $(CC) $(CFLAG_OBJ) $< $(CFLAGS_POSTFIX)
//......

aターゲットallの後にrw_を追加locker.o条件はコンパイラでrw_をチェックするためだけですlocker.cにリンクエラーの有無.
4.分析読み書きロック(使用場面)[作者より]
読み書きロックが十分に機能するには、2つの仮定に基づいています.-読み書きの非対称性、読み書きの回数は書く回数よりはるかに大きいです.データベースのように、ほとんどの時間はクエリーで行われますが、変更は比較的少ないため、データベースは通常、読み書きロックを使用します.-臨界領域にある時間は比較的長い.以上の実装から、読み書きロックは実際には通常のロック/ロック解除回数よりも多く、臨界領域にある時間が比較的短い場合、例えば参照カウントの変更と差が少なく、読み書きロックを使用すると、すべてが読み書きロックであっても通常のロックよりも効率が低下する.対応要件の概要の「いかなる場合でも追加のパフォーマンスオーバーヘッドは発生しません」.
『xtcxyczjh』-part-8 pnote over.を読む[2015.10.31-15:41]