システムプログラミング 演習6:スレッドセーフなデータ構造
2009年10月22日
スレッドセーフなデータ構造
スレッドセーフなデータ構造とは、
利用する側で explicit な lock, unlock を必要とすることなく、
マルチスレッドプログラムで利用できるデータ構造である。
データ競合の回避は自動的に行われる。
本演習では、良く利用される list 構造に関してスレッドセーフ版を作成する。
スレッドセーフな list 構造を作成するにあたり,以下の逐次版の linked
list (FIFO)を利用する。
ll.h
typedef struct ll ll_t;
struct ll
{
ll_t *n; /* next data */
};
typedef struct llh
{
ll_t *front; /* pointer to the first entry */
ll_t **back; /* address of storage of the last entry */
} llh_t;
/*
* +--------+------+
* | front | back +
* +--------+------+
* | \___
* V \|
* +---+ +---+ +---+
* | n +---+>n |---+>n |
* +---+ +---+ +---+
*/
void ll_init(llh_t *head);
void ll_enqueue(llh_t *head, ll_t *data);
ll_t *ll_peek(llh_t *head);
ll_t *ll_dequeue(llh_t *head);
ll_t *ll_traverse(llh_t *head, int (*func)(void *, void *), void *user);
/* make sure the list isn't corrupt and returns number of list items */
int ll_check(llh_t *head);
|
llh_t は linked list (FIFO)を管理する構造体。
ll_t はそれぞれのデータをつなぐためのポインタ。
ll_init は linked list (llh_t)の初期化を行う。
ll_enqueue は linked list にデータのポインタ(ll_t)を追加する。
ll_peek は先頭のデータのポインタを返す。
ll_dequeue はデータを取り出し、そのポインタを返す。
ll_traverse は linked list のデータそれぞれについて func を順に実行する。
func には,データ(ll_t)と ll_traverse の第三引数の user が渡される。
func が 0 を返した場合、traverse は続行。1 を返した場合、そこで終了。
-1 を返した場合、そのデータを dequeue してそのデータのポインタを返す。
ll.c
#include <stdlib.h>
#include <assert.h>
#include "ll.h"
void
ll_init(llh_t *head)
{
head->back = &head->front;
head->front = NULL;
}
void
ll_enqueue(llh_t *head, ll_t *data)
{
data->n = NULL;
*head->back = data;
head->back = &data->n;
}
ll_t *
ll_peek(llh_t *head)
{
return (head->front);
}
ll_t *
ll_dequeue(llh_t *head)
{
ll_t *ptr;
ptr = head->front;
if (ptr && ((head->front = ptr->n) == NULL))
/* last data, change to the initial state */
head->back = &head->front;
return (ptr);
}
ll_t *
ll_traverse(llh_t *head, int (*func)(void *, void *), void *user)
{
ll_t *ptr, **prev;
prev = &head->front;
ptr = head->front;
while (ptr) {
switch (func(ptr, user)) {
case 0:
/* continues */
prev = &ptr->n;
ptr = ptr->n;
break;
case -1:
/* the item is deleted */
if ((*prev = ptr->n) == NULL)
head->back = prev;
return (ptr);
case 1:
default:
/* traversal stops */
return (NULL);
}
}
return (NULL);
}
/* make sure the list isn't corrupt and returns number of list items */
int
ll_check(llh_t *head)
{
int i = 0;
ll_t *ptr, **prev;
prev = &head->front;
ptr = head->front;
while (ptr) {
++i;
prev = &ptr->n;
ptr = ptr->n;
}
assert(head->back == prev);
return (i);
}
|
上記の逐次版 linked list を用いて逐次版 list 構造を以下のように定義する。
slist.h
typedef struct list {
int count;
llh_t head;
} list_t;
int list_init(list_t *ptr);
int list_add(list_t *ptr, void *item);
int list_traverse(list_t *ptr, int (*func)(void *, void *), void *user);
int list_remove(list_t *ptr, void *item);
int list_destroy(list_t *ptr);
|
list 構造体は、list の要素数と linked list を管理する構造体からなる。
list_init は list 構造体の初期化を行う。
list_add は list に item (void のポインタ)を追加する。
list_traverse は、list のそれぞれの item について func を実行する。
func の第一引数は item、第二引数は list_traverse の第三引数の user である。
func が 0 を返した場合、traverse は続行。1 を返した場合、そこで終了。
-1 を返した場合、その item を消去して終了。
このとき list_traverse は -1 を返す。
list_remove は list から item を削除する。
list_destroy は list を解放する。
slist.c
#include <stdlib.h>
#include <assert.h>
#include "ll.h"
#include "slist.h"
typedef struct list_entry {
ll_t list;
void *data;
} list_entry_t;
int
list_init(list_t *ptr)
{
ptr->count = 0;
ll_init(&ptr->head);
return (0);
}
int
list_add(list_t *ptr, void *item)
{
list_entry_t *e = malloc(sizeof(*e));
if (e == NULL)
return (-1);
e->data = item;
++ptr->count;
ll_enqueue(&ptr->head, &e->list);
return (0);
}
struct wrap {
int (*func)(void *, void *);
void *user;
};
static int
wrapper(void *e, void *u)
{
list_entry_t *q = e;
struct wrap *w = u;
return (w->func(q->data, w->user));
}
int
list_traverse(list_t *ptr, int (*func)(void *, void *), void *user)
{
list_entry_t *ret;
struct wrap wrap;
wrap.func = func;
wrap.user = user;
ret = (list_entry_t *)ll_traverse(&ptr->head, wrapper, &wrap);
if (ret) {
free(ret);
--ptr->count;
return (-1);
}
return (0);
}
static int
matchit(void *data, void *compare)
{
return (data == compare ? -1 : 0);
}
int
list_remove(list_t *ptr, void *item)
{
return (list_traverse(ptr, matchit, item));
}
int
list_destroy(list_t *ptr)
{
list_entry_t *e;
while ((e = (list_entry_t *)ll_dequeue(&ptr->head)) != NULL)
free(e);
return (0);
}
#ifdef LIST_TEST
#include <stdio.h>
int
print_entry(void *entry, void *arg)
{
printf("<%s>\n", (char *)entry);
return (0);
}
int
main()
{
list_t list;
list_init(&list);
list_add(&list, "first");
list_add(&list, "second");
list_add(&list, "third");
list_traverse(&list, &print_entry, NULL);
list_destroy(&list);
exit(0);
}
#endif
|
Thread-safe List 構造
スレッドセーフ版 list 構造のヘッダファイルを以下のように定義する。
list.h
/* thread-safe verison of linked list */
typedef struct list {
int count;
pthread_mutex_t lock;
llh_t head;
} list_t;
int list_init(list_t *ptr);
int list_add(list_t *ptr, void *item);
int list_traverse(list_t *ptr, int (*func)(void *, void *), void *user);
int list_remove(list_t *ptr, void *item);
int list_destroy(list_t *ptr);
|
スレッドセーフ版の list構造では、スレッド間で共有される変数のアクセス
に対する競合状態を回避するため mutex lock を用いる。
list 構造の操作に関しては逐次版と同様である。
list.c の一部を示す。
list.c
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#include "ll.h"
#include "list.h"
typedef struct list_entry {
ll_t list;
void *data;
} list_entry_t;
/* call while holding lock */
static int
list_check(list_t *ptr)
{
assert(ptr->count == ll_check(&ptr->head));
return (1);
}
int
list_init(list_t *ptr)
{}
int
list_add(list_t *ptr, void *item)
{}
struct wrap {
int (*func)(void *, void *);
void *user;
};
static int
wrapper(void *e, void *u)
{
list_entry_t *q = e;
struct wrap *w = u;
return (w->func(q->data, w->user));
}
int
list_traverse(list_t *ptr, int (*func)(void *, void *), void *user)
{
list_entry_t *ret;
struct wrap wrap;
int return_value = 0;
wrap.func = func;
wrap.user = user;
pthread_mutex_lock(&ptr->lock);
assert(list_check(ptr));
ret = (list_entry_t *)ll_traverse(&ptr->head, wrapper, &wrap);
if (ret) {
free(ret);
--ptr->count;
return_value = -1;
}
assert(list_check(ptr));
pthread_mutex_unlock(&ptr->lock);
return (return_value);
}
int
list_remove(list_t *ptr, void *item)
{}
int
list_destroy(list_t *ptr)
{
list_entry_t *e;
while ((e = (list_entry_t *)ll_dequeue(&ptr->head)) != NULL)
free(e);
return (pthread_mutex_destroy(&ptr->lock));
}
|
演習問題
- list.cのlist_init(), list_add(), list_remove()を実装しなさい.
- スレッドセーフ版の list 構造を利用するマルチスレッドの
サンプルプログラムを作成し,動作の確認を行いなさい.
提出締切は 10/28(水) 20:00とする.
サブジェクトを SP6 学籍番号 とし,tatebe _at_ cs までメールすること.
Osamu Tatebe
Last modified: Thu Oct 22 07:07:04 JST 2009