笔记014 - HW6: Threads and Locking

题意

提供一个指针数组struct entry *table[NBUCKET],启动多个thread按照key % NBUCKET的结果往指针数组的某一个项的链表插入entry。由于并发过程中没有进行锁控制,如果多个thread同时操作同一个链表,可能会导致某个线程插入失败,例如:

1
2
3
4
5
6
7
8
t-A: calls insert() on bucket 1 (key = 6, 6 % NBUCKETS = 1)
t-B: calls insert() on bucket 1 (key = 21, 21 % NBUCKETS = 1)
...
...
t-A: executes e->next = n
t-B: executes e->next = n
t-A: executes *p = e
t-B: executes *p = e

t-A在执行e->next = n之后、执行*p = e之前,t-B执行了e->next = n,从而导致t-A没有成功插入。

解决思路

可参考的函数使用:

1
2
3
4
5
互斥锁的使用:
pthread_mutex_t lock; // declare a lock
pthread_mutex_init(&lock, NULL); // initialize the lock
pthread_mutex_lock(&lock); // acquire lock
pthread_mutex_unlock(&lock); // release lock

在put函数中添加锁控制。

1
2
3
4
5
6
7
8
9
10
static 
void put(int key, int value)
{
pthread_mutex_lock(&lock); // acquire lock

int i = key % NBUCKET;
insert(key, value, &table[i], table[i]);

pthread_mutex_unlock(&lock); // release lock
}

编译并运行:

1
2
$ gcc -g -O2 ph.c -pthread
$ ./a.out 2

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>

#define SOL
#define NBUCKET 5
#define NKEYS 100000

struct entry {
int key;
int value;
struct entry *next;
};
struct entry *table[NBUCKET];
int keys[NKEYS];
int nthread = 1;
volatile int done;

pthread_mutex_t lock; // declare a lock

double
now()
{
struct timeval tv;
gettimeofday(&tv, 0);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}

static void
print(void)
{
int i;
struct entry *e;
for (i = 0; i < NBUCKET; i++) {
printf("%d: ", i);
for (e = table[i]; e != 0; e = e->next) {
printf("%d ", e->key);
}
printf("\n");
}
}

static void
insert(int key, int value, struct entry **p, struct entry *n)
{
struct entry *e = malloc(sizeof(struct entry));
e->key = key;
e->value = value;
e->next = n;
*p = e;
}

static
void put(int key, int value)
{
pthread_mutex_lock(&lock); // acquire lock

int i = key % NBUCKET;
insert(key, value, &table[i], table[i]);

pthread_mutex_unlock(&lock); // release lock
}

static struct entry*
get(int key)
{
struct entry *e = 0;
for (e = table[key % NBUCKET]; e != 0; e = e->next) {
if (e->key == key) break;
}
return e;
}

static void *
thread(void *xa)
{
long n = (long) xa;
int i;
int b = NKEYS/nthread;
int k = 0;
double t1, t0;

// printf("b = %d\n", b);
t0 = now();
for (i = 0; i < b; i++) {
// printf("%d: put %d\n", n, b*n+i);
put(keys[b*n + i], n);
}
t1 = now();
printf("%ld: put time = %f\n", n, t1-t0);

// Should use pthread_barrier, but MacOS doesn't support it ...
__sync_fetch_and_add(&done, 1);
while (done < nthread) ;

t0 = now();
for (i = 0; i < NKEYS; i++) {
struct entry *e = get(keys[i]);
if (e == 0) k++;
}
t1 = now();
printf("%ld: lookup time = %f\n", n, t1-t0);
printf("%ld: %d keys missing\n", n, k);
return NULL;
}

int
main(int argc, char *argv[])
{
pthread_mutex_init(&lock, NULL); // initialize the lock

pthread_t *tha;
void *value;
long i;
double t1, t0;

if (argc < 2) {
fprintf(stderr, "%s: %s nthread\n", argv[0], argv[0]);
exit(-1);
}
nthread = atoi(argv[1]);
tha = malloc(sizeof(pthread_t) * nthread);
srandom(0);
assert(NKEYS % nthread == 0);
for (i = 0; i < NKEYS; i++) {
keys[i] = random();
}
t0 = now();
for(i = 0; i < nthread; i++) {
assert(pthread_create(&tha[i], NULL, thread, (void *) i) == 0);
}
for(i = 0; i < nthread; i++) {
assert(pthread_join(tha[i], &value) == 0);
}
t1 = now();
printf("completion time = %f\n", t1-t0);
}
显示 Gitment 评论