|
|
単方向リスト: 並行処理編 Part 2ソースはGitHubに移行しました。 Lazy Synchronization Singly-linked List(Lazy同期 単方向リスト) pthread_mutex編以下の論文で提案されたLazy同期リストを実装する。 S. Heller, M. Herlihy, V. Luchangco, M.Moir, W. N. Scherer III, N. Shavit, "A Lazy Concurrent List-Based Set Algorithm", Proc. of the Ninth International Conference on Principles of Distributed Systems, Pisa, Italy, pp.3-16, 2005"A Lazy Concurrent List-Based Set Algorithm"
基本戦略は細粒度単方向リストの改良で、リストの走査はノードをロックせず"楽観的(Optimistic)"に"怠惰(Lazy)"に行い、
見つかったノードだけロックして走査(add/delete)する。
ただし、ロックをかけるタイミングによっては削除するノードの次に新ノードを追加してしまう可能性もあるので、 削除するノードにマーキングして"論理的に削除"し、追加と削除を並行実行しても不都合が起きないようにしている。 論文は擬似Javaで記述されているので、Cで再実装した。 ソースはGitHubに移行しました。 データ構造データ構造list_tとnode_tを定義する。 list_tはListの本体、node_tはListに連結するnodeのデータ構造である。 nodeとlistのデータ構造を示す。 LazySynchroList.h: typedef struct _node_t { lkey_t key; /* key */ val_t val; /* 値 */ bool_t marked; /* 論理的に削除されたか否かのmark */ pthread_mutex_t mtx; /* node毎のlock */ struct _node_t *next; /* 次のnodeへのポインタ */ } node_t; typedef struct _list_t { node_t *head; node_t *tail; } list_t; node_tは(key,val)のペアと次のnodeへのポインタ、およびpthreadのmutex変数を保持する。 また、論理的な削除を示すbool値markedも保持する。初期値はfalseで"削除されていない"を示す。 list_tには、リストの先頭headと末尾tailが確保される。 基本関数listの生成/* * list_t *init_list(void) * * listを生成する。 * * 成功すればlistへのポインタを返す。失敗ならNULLを返す。 * */ list_t *init_list(void) { list_t *list; if ((list = (list_t *) calloc(1, sizeof(list_t))) == NULL) { elog("calloc error"); return NULL; } if ((list->head = (node_t *) calloc(1, sizeof(node_t))) == NULL) { elog("calloc error"); free (list); goto end; } list->head->mtx = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; if ((list->tail = (node_t *) calloc(1, sizeof(node_t))) == NULL) { elog("calloc error"); goto end; } list->tail->mtx = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; list->head->next = list->tail; list->tail->next = NULL; return list; end : free (list->head); free (list); return NULL; } ノードの追加
ノードの追加を行う関数add()を示す。 /* * bool_t add(list_t * list, const lkey_t key, const val_t val) * * listにnode(key,val)を追加する。 * 追加位置はkeyの昇順に従う。すでに同じkeyを持つnodeがある場合は失敗。 * * 成功すればtrue、失敗すればfalseを返す。 */ #define validate(pred, curr) (!pred->marked && !curr->marked && pred->next == curr) bool_t add(list_t * l, const lkey_t key, const val_t val) { node_t *pred, *curr; node_t *newNode; bool_t ret = true; if ((newNode = create_node(key, val)) == NULL) return false; while (1) { pred = l->head; curr = pred->next; /* 目的のノードまで走査 */ while (curr->key < key && curr != l->tail) { pred = curr; curr = pred->next; } /* * この間に、他のスレッドがpredやcurrを削除、変更、または * ノードを追加する可能性がある */ if (lock(&pred->mtx) != 0) { /* lockが成功しなければ、最初からやりなおし */ continue; } else if (lock(&curr->mtx) != 0) { unlock(&pred->mtx); continue; } /* この時点で成立する条件式: (pred->key) < (newNode->key) <= (curr->key) */ assert ((pred->key < key) && (key <= curr->key)); /* クリティカルセクション開始 */ if (validate(pred, curr)) { if (key == curr->key) { ret = false; free_node(newNode); } else { newNode->next = curr; pred->next = newNode; } /* クリティカルセクション終了 */ unlock(&pred->mtx); unlock(&curr->mtx); break; } unlock(&pred->mtx); unlock(&curr->mtx); } return ret; }関数add()内部で呼ぶnode生成関数create_node()を示す。 /* * node_t *create_node(const lkey_t key, const val_t val) * * (key, val)を持つnodeを生成する。 * * 成功すればnodeへのポインタを返す。失敗すればNULLを返す。 */ static node_t *create_node(const lkey_t key, const val_t val) { node_t *node; if ((node = calloc(1, sizeof(node_t))) == NULL) { elog("calloc error"); return NULL; } node->mtx = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; node->key = key; node->val = val; return node; } ノードの削除
ノードの削除も基本戦略は追加と同じである。よって実装にも上と同じ問題を抱えている。 /* * bool_t delete(list_t * list, const lkey_t key, val_t *val) * * listからkeyを持つnodeを削除し、そのnodeのvalを*valに書き込む。 * keyが存在しない場合は失敗。 * * 成功すればtrue、失敗すればfalseを返す。 */ #define validate(pred, curr) (!pred->marked && !curr->marked && pred->next == curr) bool_t delete(list_t * l, const lkey_t key, val_t *val) { node_t *pred, *curr; bool_t ret = true; while(1) { pred = l->head; curr = pred->next; if (curr == l->tail) { ret = false; break; } else { /* 目的のノードまで走査 */ while (curr->key < key && curr != l->tail) { pred = curr; curr = pred->next; } /* * この間に、他のスレッドがpredやcurrを削除、変更、または * ノードを追加する可能性がある */ if (lock(&pred->mtx) != 0) { /* lockが成功しなければ、最初からやりなおし */ continue; } else if (lock(&curr->mtx) != 0) { unlock(&pred->mtx); continue; } /* この時点で成立する条件式: (pred->key) < (newNode->key) <= (curr->key) */ assert ((pred->key < key) && (key <= curr->key)); /* クリティカルセクション開始 */ if (validate(pred, curr)) { if (key == curr->key) { curr->marked = true; *val = curr->val; pred->next = curr->next; free_node(curr); } else { ret = false; } /* クリティカルセクション終了 */ unlock(&pred->mtx); unlock(&curr->mtx); break; } unlock(&pred->mtx); unlock(&curr->mtx); } } return ret; } ノードの検索/* * bool_t find(list_t * list, const lkey_t key, val_t *val) * * listからkeyを持つnodeを検索し、そのnodeのvalを*valに書き込む。 * keyが存在しない場合は失敗。 * * 成功すればtrue、失敗すればfalseを返す。 */ bool_t find(const list_t * list, const lkey_t key) { node_t *curr; curr = list->head; while (curr->key < key) { curr = curr->next; } return (curr->key == key && !curr->marked); } 実行ソースはGitHubに移行しました。
Last-modified: 2014-7-6
|