ostep28.14使用pack和unpack

ostep28.14使用pack和unpack

存在自旋,但是自旋仅在guard保护修改flag和队列中,比用户的临界区短

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
1  typedef struct __lock_t {
2 int flag;
3 int guard;
4 queue_t *q;
5 } lock_t;

6 void lock_init(lock_t *m) {
7 m->flag = 0;
8 m->guard = 0;
9 queue_init(m->q);
10 }

11 void lock(lock_t *m) {
12 while (TestAndSet(&m->guard, 1) == 1) // acquire guard lock by spinning
13 ; // 自旋直到获取到guard锁
14 if (m->flag == 0) { // 如果flag为0,表示锁未被占用
15 m->flag = 1; // 获取锁
16 m->guard = 0; // 释放guard锁
17 } else { // 锁已被占用
18 queue_add(m->q, gettid()); // 将当前线程加入等待队列
19 m->guard = 0; // 释放guard锁
20 park(); // 将线程挂起,进入睡眠状态
21 }
22 }

23 void unlock(lock_t *m) {
24 while (TestAndSet(&m->guard, 1) == 1) // acquire guard lock by spinning
25 ; // 自旋直到获取到guard锁
26 if (queue_empty(m->q)) { // 如果队列为空,表示没有等待线程
27 m->flag = 0; // 释放锁
28 } else {
29 unpark(queue_remove(m->q)); // 唤醒等待队列中的下一个线程
30 }
31 m->guard = 0; // 释放guard锁
32 }

guard的作用是保护flag和队列的修改,flag保护临界区

在unlock中,唤醒等待线程后,不需要设置flag=1,这样直接将锁从解锁的线程传给了被唤醒的线程。

是的,在原文的代码实现中,线程被唤醒后就直接进入临界区

存在的问题就是唤醒的丢失,因为A线程加入队列后如果切换到释放锁的线程B,线程B唤醒A后,A继续执行然后睡眠,但是此时等待队列已经没有A了,所以会造成死锁

使用setpark,如果setpark后切换到另一个线程,调用unpark释放后,返回lock函数,pack会直接返回而不再睡眠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void lock(lock_t *m) {
while (TestAndSet(&m->guard, 1) == 1)
; // 自旋等待获取guard锁

if (m->flag == 0) { // 如果锁未被占用
m->flag = 1; // 获取锁
m->guard = 0; // 释放guard锁
} else { // 如果锁被占用
queue_add(m->q, gettid()); // 将当前线程加入等待队列
setpark(); // 设置park状态,防止丢失唤醒
m->guard = 0; // 释放guard锁
park(); // 挂起线程,等待被唤醒
// 唤醒后继续执行
}
}