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) 13 ; 14 if (m->flag == 0) { 15 m->flag = 1; 16 m->guard = 0; 17 } else { 18 queue_add(m->q, gettid()); 19 m->guard = 0; 20 park(); 21 } 22 }
23 void unlock(lock_t *m) { 24 while (TestAndSet(&m->guard, 1) == 1) 25 ; 26 if (queue_empty(m->q)) { 27 m->flag = 0; 28 } else { 29 unpark(queue_remove(m->q)); 30 } 31 m->guard = 0; 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) ; if (m->flag == 0) { m->flag = 1; m->guard = 0; } else { queue_add(m->q, gettid()); setpark(); m->guard = 0; park(); } }
|