Operating System Assignments Chapter 26-31

The third assignment

Chapter 26

The title code of this chapter contains a simulator x86.py. x86.py serves as a simulator and runs assembly code including loop.s. Some important parameters are as follows:

  • -p: Specify program
  • -t: number of threads
  • -i: Specifies that an interrupt will be generated every time i instructions are executed.
  • -M: track memory
  • -R: Track specific register values
  • -C: Track the value of the condition code
  • -r: interrupt randomly

1. Question 1

The loop.s program mentioned in the question is as follows.

.main
.top
sub  $1,%dx
test $0,%dx     
jgte .top         
halt

This program implements a simple loop of single thread. The value in the dx register is the loop count. The value of dx in each loop is -1, which is compared with 0. If it is greater than or equal to 0, jump and continue the loop. The dx value is 0 and enters the loop, that is, dx=-1 in the last loop. The cycle ends.

The given parameters are: ./x86.y -p loop.s -t 1 -i 100 -R dx, which means that executing loop.s as a single thread, 100 instructions will generate an interrupt (a single process will not occur Thread switching), tracking register dx. There are 3 instructions in the loop at a time, so every three instructions, the value of dx is -1. When the value of dx is -1, the loop ends. Run the program and get the following results:

   dx          Thread 0         
    ?   
    ?   1000 sub  $1,%dx
    ?   1001 test $0,%dx
    ?   1002 jgte .top
    ?   1003 halt

It ends after executing only 4 instructions. The initial value of dx should be 0. After entering the loop, it is reduced to -1 and the loop is exited directly. Checked using the -c flag, the value of dx is as expected.

   dx          Thread 0         
    0   
   -1   1000 sub  $1,%dx
   -1   1001 test $0,%dx
   -1   1002 jgte .top
   -1   1003 halt

2. Question 2

Run loop.s similarly, this time the parameters are: ./x86.py -p loop.s -t 2 -i 100 -a dx=3,dx=3 -R dx, that is, dx is initialized to 3, run 2 same thread. When multiple threads are running, scheduling may occur on interrupts. But since dx is initialized to 3, the execution of one thread only requires 10 instructions, and the interrupt generation interval is 100 instructions, so the two threads will run separately, each executing 13 instructions (3 loops, 3 instructions each time, It ends after exiting the loop for the last time and executing 4 instructions), and the running situation is the same. Use the -c flag to verify:

   dx          Thread 0                Thread 1         
    3   
    2   1000 sub  $1,%dx
    2   1001 test $0,%dx
    2   1002 jgte .top
    1   1000 sub  $1,%dx
    1   1001 test $0,%dx
    1   1002 jgte .top
    0   1000 sub  $1,%dx
    0   1001 test $0,%dx
    0   1002 jgte .top
   -1   1000 sub  $1,%dx
   -1   1001 test $0,%dx
   -1   1002 jgte .top
   -1   1003 halt
    3   ----- Halt;Switch -----  ----- Halt;Switch -----  
    2                            1000 sub  $1,%dx
    2                            1001 test $0,%dx
    2                            1002 jgte .top
    1                            1000 sub  $1,%dx
    1                            1001 test $0,%dx
    1                            1002 jgte .top
    0                            1000 sub  $1,%dx
    0                            1001 test $0,%dx
    0                            1002 jgte .top
   -1                            1000 sub  $1,%dx
   -1                            1001 test $0,%dx
   -1                            1002 jgte .top
   -1                            1003 halt

The simultaneous existence of these two threads will not affect the calculation. There is no race condition in this code, and no shared data is used. The value in the register will be saved and restored when the thread is switched, and will not affect the operation.

3. Question 3

The parameters specified in this question are: ./x86.py -p loop.s -t 2 -i 3 -r -a dx=3,dx=3 -R dx, the interruption interval is 3 and random. However, since there is no race condition in the code in loop.s, the interruption interval will not affect the running of the two threads. Use c flag to verify:

   dx          Thread 0                Thread 1         
    3   
    2   1000 sub  $1,%dx
    2   1001 test $0,%dx
    2   1002 jgte .top
    3   ------ Interrupt ------  ------ Interrupt ------  
    2                            1000 sub  $1,%dx
    2                            1001 test $0,%dx
    2                            1002 jgte .top
    2   ------ Interrupt ------  ------ Interrupt ------  
    1   1000 sub  $1,%dx
    1   1001 test $0,%dx
    2   ------ Interrupt ------  ------ Interrupt ------  
    1                            1000 sub  $1,%dx
    1   ------ Interrupt ------  ------ Interrupt ------  
    1   1002 jgte .top
    0   1000 sub  $1,%dx
    1   ------ Interrupt ------  ------ Interrupt ------  
    1                            1001 test $0,%dx
    1                            1002 jgte .top
    0   ------ Interrupt ------  ------ Interrupt ------  
    0   1001 test $0,%dx
    0   1002 jgte .top
   -1   1000 sub  $1,%dx
    1   ------ Interrupt ------  ------ Interrupt ------  
    0                            1000 sub  $1,%dx
   -1   ------ Interrupt ------  ------ Interrupt ------  
   -1   1001 test $0,%dx
   -1   1002 jgte .top
    0   ------ Interrupt ------  ------ Interrupt ------  
    0                            1001 test $0,%dx
    0                            1002 jgte .top
   -1   ------ Interrupt ------  ------ Interrupt ------  
   -1   1003 halt
    0   ----- Halt;Switch -----  ----- Halt;Switch -----  
   -1                            1000 sub  $1,%dx
   -1                            1001 test $0,%dx
   -1   ------ Interrupt ------  ------ Interrupt ------  
   -1                            1002 jgte .top
   -1                            1003 halt

It can be seen that although an interruption occurs, the running status of each thread is the same as in question 2, that is, the loop is performed normally. Using different seeds and modifying the interrupt interval will not affect the operation of each thread.

4. Question 4

What needs to be run in this question is looping-race-nolock.s:

.main
.top	
# critical section
mov 2000, %ax  # get 'value' at address 2000
add $1, %ax    # increment it
mov %ax, 2000  # store it back

# see if we're still looping
sub  $1, %bx
test $0, %bx
jgt .top	

halt

This program takes the value at address 2000 and puts it in the ax register, adds 1 and stores it back to address 2000, and performs this operation in a loop. bx is still used for loop counting.

Run this program. The parameters given in the question are: ./x86.py -p looping-race-nolock.s -t 1 -M 2000. Run this program in a single thread. Let the value of memory address 2000 be x, and then As the loop continues (every 6 instructions), the value of x is incremented until the end of the loop. The final program implements the operation of x = the value of the original x + dx register. Use the -c flag to verify:

 2000          Thread 0         
    0   
    0   1000 mov 2000, %ax
    0   1001 add $1, %ax
    1   1002 mov %ax, 2000
    1   1003 sub  $1, %bx
    1   1004 test $0, %bx
    1   1005 jgt .top
    1   1006 halt

The value at address 2000 is 0 and the dx value is 1, so it only loops once. After the mov instruction stores the value from the ax register to address 2000, the final value is 1.

This code actually has a race condition, because if two identical threads run, they will operate on the shared data at address 2000(x). The correct effect of two threads running should be the final value of x=original x+2dx, but entering the critical section at the same time will cause an error to occur.

#临界区
mov 2000, %ax  # get 'value' at address 2000
add $1, %ax    # increment it
mov %ax, 2000  # store it back

The errors that may occur are as follows:

Thread 1 Thread 2 Data at address 2000 ax register
mov 2000, %ax
add $1, %ax
x x+1
mov 2000, %ax
add $1, %ax
mov %ax, 2000
x+1 x+1
mov %ax, 2000 x+1 x+1

In one cycle of each of the two threads, scheduling occurred. x=x+2 should have been implemented in two separate cycles, but the wrong result x=x+1 was produced. This is what the race condition here may cause. mistake.

Chapter 28

This chapter also uses x86.py to simulate running some programs and observe the running conditions and results.

1. Question 1

flag.s is as follows:

.var flag
.var count

.main
.top

.acquire
mov  flag, %ax      # get flag
test $0, %ax        # if we get 0 back: lock is free!
jne  .acquire       # if not, try again
mov  $1, flag       # store 1 into flag

# critical section
mov  count, %ax     # get the value at the address
add  $1, %ax        # increment it
mov  %ax, count     # store it back

# release lock
mov  $0, flag       # clear the flag now

# see if we're still looping
sub  $1, %bx
test $0, %bx
jgt .top	

halt

This code transfers the variable count to the ax register each time it loops, adds 1 and then saves the value. The flag variable is used to implement the lock. Whenever the count variable is operated, first determine flag = 0. If it is not 0, wait in a loop. If flag = 0, set the flag to 1, operate the count, add 1 and save the flag. Set to 0. Try to implement locking through the flag variable to protect the critical section code. However, there is a problem with this implementation, and errors similar to the following will occur:

Thread 1 Thread 2
mov flag, %ax
test $0, %ax
mov flag, %ax
test $0, %ax
mov $1, flag
mov $1, flag

​ When executed in the above order, there will be a situation where both threads set the flag to 1 and can enter the critical section. Therefore, this way of implementing the lock is incorrect.

2. Question 2

Use the default value and specify 2 threads to run: ./x86 -p flag.s -t 2. The operation situation is as follows:

       Thread 0                Thread 1         
1000 mov  flag, %ax
1001 test $0, %ax
1002 jne  .acquire
1003 mov  $1, flag
1004 mov  count, %ax
1005 add  $1, %ax
1006 mov  %ax, count
1007 mov  $0, flag
1008 sub  $1, %bx
1009 test $0, %bx
1010 jgt .top
1011 halt
----- Halt;Switch -----  ----- Halt;Switch -----  
                         1000 mov  flag, %ax
                         1001 test $0, %ax
                         1002 jne  .acquire
                         1003 mov  $1, flag
                         1004 mov  count, %ax
                         1005 add  $1, %ax
                         1006 mov  %ax, count
                         1007 mov  $0, flag
                         1008 sub  $1, %bx
                         1009 test $0, %bx
                         1010 jgt .top
                         1011 halt

By default, the interrupt interval is 50, and there is no interruption in the thread running. The two threads run successively without errors. The final flag flag will be set to 0 in the last cycle of thread 2, and count=count+2*bx value. Verify using ./x86 -p flag.s -t 2 -M count -R bx -c:

count      bx          Thread 0                Thread 1         

    0       0   
    0       0   1000 mov  flag, %ax
    0       0   1001 test $0, %ax
    0       0   1002 jne  .acquire
    0       0   1003 mov  $1, flag
    0       0   1004 mov  count, %ax
    0       0   1005 add  $1, %ax
    1       0   1006 mov  %ax, count
    1       0   1007 mov  $0, flag
    1      -1   1008 sub  $1, %bx
    1      -1   1009 test $0, %bx
    1      -1   1010 jgt .top
    1      -1   1011 halt
    1       0   ----- Halt;Switch -----  ----- Halt;Switch -----  
    1       0                            1000 mov  flag, %ax
    1       0                            1001 test $0, %ax
    1       0                            1002 jne  .acquire
    1       0                            1003 mov  $1, flag
    1       0                            1004 mov  count, %ax
    1       0                            1005 add  $1, %ax
    2       0                            1006 mov  %ax, count
    2       0                            1007 mov  $0, flag
    2      -1                            1008 sub  $1, %bx
    2      -1                            1009 test $0, %bx
    2      -1                            1010 jgt .top
    2      -1                            1011 halt

The count value is 0, the bx value is 0, each thread has only one count+1, and the final count value is 2. Both threads were running without interruption.

3. Question 3

Set the bx value to 2 and run two threads, then each thread implements count=count+2, and finally count+=4. In Question 1, we have analyzed that there is a race condition in the code executed by the thread. However, since the interruption occurs only after 50 instructions, the two threads here still run one after another without causing an error. The final count=4. Use ./x86 -p flag.s -t 2 -a bx=2,bx=2 -M count -c to run the program to verify:

   0   
    0   1000 mov  flag, %ax
    0   1001 test $0, %ax
    0   1002 jne  .acquire
    0   1003 mov  $1, flag
    0   1004 mov  count, %ax
    0   1005 add  $1, %ax
    1   1006 mov  %ax, count
    1   1007 mov  $0, flag
    1   1008 sub  $1, %bx
    1   1009 test $0, %bx
    1   1010 jgt .top
    1   1000 mov  flag, %ax
    1   1001 test $0, %ax
    1   1002 jne  .acquire
    1   1003 mov  $1, flag
    1   1004 mov  count, %ax
    1   1005 add  $1, %ax
    2   1006 mov  %ax, count
    2   1007 mov  $0, flag
    2   1008 sub  $1, %bx
    2   1009 test $0, %bx
    2   1010 jgt .top
    2   1011 halt
    2   ----- Halt;Switch -----  ----- Halt;Switch -----  
    2                            1000 mov  flag, %ax
    2                            1001 test $0, %ax
    2                            1002 jne  .acquire
    2                            1003 mov  $1, flag
    2                            1004 mov  count, %ax
    2                            1005 add  $1, %ax
    3                            1006 mov  %ax, count
    3                            1007 mov  $0, flag
    3                            1008 sub  $1, %bx
    3                            1009 test $0, %bx
    3                            1010 jgt .top
    3                            1000 mov  flag, %ax
    3                            1001 test $0, %ax
    3                            1002 jne  .acquire
    3                            1003 mov  $1, flag
    3                            1004 mov  count, %ax
    3                            1005 add  $1, %ax
    4                            1006 mov  %ax, count
    4                            1007 mov  $0, flag
    4                            1008 sub  $1, %bx
    4                            1009 test $0, %bx
    4                            1010 jgt .top
    4                            1011 halt

4. Question 4

Set bx to a high value, that is, the thread will execute many instructions, and then change the interrupt frequency. Thread switching will occur at some specific locations during interrupts, which will cause the possible problems pointed out in question 1. The two threads should have implemented count =count+2bx, but the running result may be count<count+2bx. Test using different bx values ​​and interrupt frequencies. The results are as follows:

bx Interrupt interval Result(count)
1000 20 1779
1000 30 1802
1000 40 1776
1000 50 1985
1000 60 1904
1000 70 1746
1000 80 1800
1000 90 1802
1000 100 1810

Judging from the occurrence of errors, it should be that the smaller the interruption interval and the more thread switches, the more likely it is that errors will occur. But in fact, under some interrupt intervals, the interrupt may always occur at the location where the flag is set, which will produce bad results. Under some interrupt intervals, the interrupt will always not occur at the location where the flag is set, and the results may be better. . Therefore, the trend that the smaller the interruption interval is, the worse the results are is not obvious.

5. Question 5

The program in test-and-set.s is as follows:

.var mutex
.var count

.main
.top	

.acquire
mov  $1, %ax        
xchg %ax, mutex     # atomic swap of 1 and mutex
test $0, %ax        # if we get 0 back: lock is free!
jne  .acquire       # if not, try again

# critical section
mov  count, %ax     # get the value at the address
add  $1, %ax        # increment it
mov  %ax, count     # store it back

# release lock
mov  $0, mutex

# see if we're still looping
sub  $1, %bx
test $0, %bx
jgt .top	

halt

Use the xchg instruction to implement the lock. When locking, set the ax register to 1. Use the xchg instruction to exchange the values ​​of the mutex and ax registers. If the ax value is 0, the lock is successfully acquired. When releasing the lock, set mutex to 0 directly.

6. Question 6

​ Run two threads, change the interrupt interval, and run test-and-set.s. The results are as follows:

bx Interrupt interval Result(count)
1000 10 2000
1000 20 2000
1000 30 2000
1000 40 2000
1000 50 2000
1000 60 2000
1000 70 2000
1000 80 2000
1000 90 2000
1000 100 2000

No matter how large the interruption interval is, the code can always work as expected because the lock implemented by xchg can provide mutual exclusion and the two threads will not enter the critical section at the same time. Under the implementation of this kind of lock, sometimes the CPU utilization is not high, because if the lock is occupied by another thread, the current thread needs to wait in a loop until another thread is scheduled and the other thread releases the lock before it can acquire the lock and continue running. The waiting time of the loop is the reason why the CPU utilization is not high. It only takes 6 instructions to complete a complete loop calculation, and 11 instructions including acquiring and releasing the lock. Taking the ratio, only 55% of the time is used by the CPU to complete key operations. Considering that acquiring the lock still requires waiting and completing a cycle calculation requires more instructions, the CPU utilization can also be quantified by comparing the 11 instructions expected to acquire the lock and complete a calculation at one time with the instructions that actually complete a calculation. .

7. Question 7

在第一个线程获取锁之后,第二个线程应该无法获取锁,因此不停尝试获取锁,不能进入临界区。运行两个线程,设置bx=1,让一个线程先获取锁,另一个线程应该在不断尝试获取锁,因此设置线程运行序列为00(线程0获取锁)111111110000001111111111110000,./x86.py -p test-and-set.s -t 2 -a bx=1,bx=1 -P 00111111110000001111111111110000 -M mutex -c运行程序,结果是正确的,线程1自旋等待线程0释放锁。

mutex          Thread 0                Thread 1         

    0   
    0   1000 mov  $1, %ax
    1   1001 xchg %ax, mutex
    1   ------ Interrupt ------  ------ Interrupt ------  
    1                            1000 mov  $1, %ax
    1                            1001 xchg %ax, mutex
    1                            1002 test $0, %ax
    1                            1003 jne  .acquire
    1                            1000 mov  $1, %ax
    1                            1001 xchg %ax, mutex
    1                            1002 test $0, %ax
    1                            1003 jne  .acquire
    1   ------ Interrupt ------  ------ Interrupt ------  
    1   1002 test $0, %ax
    1   1003 jne  .acquire
    1   1004 mov  count, %ax
    1   1005 add  $1, %ax
    1   1006 mov  %ax, count
    0   1007 mov  $0, mutex
    0   ------ Interrupt ------  ------ Interrupt ------  
    0                            1000 mov  $1, %ax
    1                            1001 xchg %ax, mutex
    1                            1002 test $0, %ax
    1                            1003 jne  .acquire
    1                            1004 mov  count, %ax
    1                            1005 add  $1, %ax
    1                            1006 mov  %ax, count
    0                            1007 mov  $0, mutex
    0                            1008 sub  $1, %bx
    0                            1009 test $0, %bx
    0                            1010 jgt .top
    0                            1011 halt
    0   ----- Halt;Switch -----  ----- Halt;Switch -----  
    0   1008 sub  $1, %bx
    0   1009 test $0, %bx
    0   1010 jgt .top
    0   1011 halt

第30章

本章题目提供的程序可以观察一些使用条件变量的代码解决的生产者消费者问题的情况,一些重要的参数如下:

  • -l:生产者生产数
  • -m:缓冲区大小
  • -p/-c:生产者和消费者数量
  • -P/-C:生产者/消费者休眠情况

1.题1

main-two-cvs-while.c程序中是使用条件变量实现的生产者/消费者问题的解决方法。使用了两个条件变量发出正确的信号唤醒线程。

pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;

缓冲区有不止一个位置,对应的fill和get的实现通过fill_ptr和use_ptr确定执行fill或get的生产者和消费者。

void do_fill(int value) {
    
    
    // ensure empty before usage
    ensure(buffer[fill_ptr] == EMPTY, "error: tried to fill a non-empty buffer");
    buffer[fill_ptr] = value;
    fill_ptr = (fill_ptr + 1) % max;
    num_full++;
}

int do_get() {
    
    
    int tmp = buffer[use_ptr];
    ensure(tmp != EMPTY, "error: tried to get an empty buffer");
    buffer[use_ptr] = EMPTY; 
    use_ptr = (use_ptr + 1) % max;
    num_full--;
    return tmp;
}

生产者的部分如下,loops为次数,每次生产时,先获取锁,根据缓冲区是否为空决定是否需要休眠等待,为空则调用do_fill生产值,使用fill唤醒消费者,并释放锁。

void *producer(void *arg) {
    
    
    int id = (int) arg;
    // make sure each producer produces unique values
    int base = id * loops; 
    int i;
    for (i = 0; i < loops; i++) {
    
       p0;
		Mutex_lock(&m);             p1;
		while (num_full == max) {
    
       p2;
	    	Cond_wait(&empty, &m);  p3;
		}
		do_fill(base + i);          p4;
		Cond_signal(&fill);         p5;
		Mutex_unlock(&m);           p6;
    }
    return NULL;
}

消费者的部分如下,消费者会一直消费至到消费到END_OF_STREAM,消费过程为上锁,根据缓冲区是否有数据,并决定是否休眠,有数据则获取数据,唤醒消费者并释放锁。

void *consumer(void *arg) {
    
    
    int id = (int) arg;
    int tmp = 0;
    int consumed_count = 0;
    while (tmp != END_OF_STREAM) {
    
     c0;
		Mutex_lock(&m);            c1;
		while (num_full == 0) {
    
        c2;
	    	Cond_wait(&fill, &m);  c3;
        }
		tmp = do_get();            c4;
		Cond_signal(&empty);       c5;
		Mutex_unlock(&m);          c6;
		consumed_count++;
    }
    // return consumer_count-1 because END_OF_STREAM does not count
    return (void *) (long long) (consumed_count - 1);
}

2.题2

根据题中要求,只有一个生产者和一个消费者,缓冲区初始大小为1,在不休眠和消费者休眠释放锁时休眠的情况下,增加缓冲区大小,增加生产数。生产者消费者的生产和消费的过程应该不受缓冲区大小和生产数量的影响,增大缓冲区和生产数,可以看到两部分代码交替执行,每次生产与消费的过程都基本相同,生产和消费交替进行,num_full在0,1,2之间变化。加入休眠,./main-two-cvs-while -l 100 -m 10 -p 1 -c 1 -v -C 0,0,0,0,0,0,1运行程序,起初生产者一直运行直到缓冲区填满,缓冲区填满后看到消费者和生产者的代码按以下顺序交替循环执行,num_full随着消费和生产在9和10之间变化,最后消费者连续消费至0:

9 [ 90 91 92 93 94 95 96 97 f— u 89 ] p3
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] p4
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] p5
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] p6
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] p0
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] p1
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] p2
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] c0
10 [ 90 91 92 93 94 95 96 97 98 * 89 ] c1
9 [u 90 91 92 93 94 95 96 97 98 f— ] c4
9 [u 90 91 92 93 94 95 96 97 98 f— ] c5
9 [u 90 91 92 93 94 95 96 97 98 f— ] c6

3.题4

题中所给参数为:./main-two-cvs-while -p 1 -c 3 -m 1 -C 0,0,0,1,0,0,0:0,0,0,1,0,0,0:0,0,0,1,0,0,0 -l 10 -v -t

一个生产者,三个消费者,缓冲区大小为1,每个消费者都在c3休眠。生产者生产10次,如果每个消费者都在c3休眠一秒,则所需时间为10s,而有时c3休眠后在while循环中,由于num_full=0再次进入c3,又休眠1s,因此最终所需要的时间应大于10s。

多次运行程序,所用时间不同,而且发现生产者总是唤醒某个特定消费者,导致总是一个消费者消费了大部分的数据。其中一次运行结果如下:

NF        P0 C0 C1 C2 
  0 [*--- ] p0
  0 [*--- ]          c0
  0 [*--- ]       c0
  0 [*--- ]    c0
  0 [*--- ] p1
  1 [*  0 ] p4
  1 [*  0 ] p5
  1 [*  0 ] p6
  1 [*  0 ]       c1
  1 [*  0 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c1
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ]          c1
  0 [*--- ]          c2
  0 [*--- ] p1
  1 [*  1 ] p4
  1 [*  1 ] p5
  1 [*  1 ] p6
  1 [*  1 ]       c1
  1 [*  1 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ] p1
  1 [*  2 ] p4
  1 [*  2 ] p5
  1 [*  2 ] p6
  1 [*  2 ]       c1
  1 [*  2 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]          c3
  0 [*--- ]       c0
  0 [*--- ]          c2
  0 [*--- ] p1
  1 [*  3 ] p4
  1 [*  3 ] p5
  1 [*  3 ] p6
  1 [*  3 ]       c1
  1 [*  3 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ] p1
  1 [*  4 ] p4
  1 [*  4 ] p5
  1 [*  4 ] p6
  1 [*  4 ]       c1
  1 [*  4 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]          c3
  0 [*--- ]       c0
  0 [*--- ]          c2
  0 [*--- ] p1
  1 [*  5 ] p4
  1 [*  5 ] p5
  1 [*  5 ] p6
  1 [*  5 ]       c1
  1 [*  5 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ] p1
  1 [*  6 ] p4
  1 [*  6 ] p5
  1 [*  6 ] p6
  1 [*  6 ]       c1
  1 [*  6 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]          c3
  0 [*--- ]       c0
  0 [*--- ]          c2
  0 [*--- ] p1
  1 [*  7 ] p4
  1 [*  7 ] p5
  1 [*  7 ] p6
  1 [*  7 ]       c1
  1 [*  7 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ] p1
  1 [*  8 ] p4
  1 [*  8 ] p5
  1 [*  8 ] p6
  1 [*  8 ]       c1
  1 [*  8 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]          c3
  0 [*--- ]       c0
  0 [*--- ]          c2
  0 [*--- ] p1
  1 [*  9 ] p4
  1 [*  9 ] p5
  1 [*  9 ] p6
  1 [*  9 ]       c1
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3
  0 [*--- ]       c0
  0 [*--- ]    c2
  1 [*EOS ] [main: added end-of-stream marker]
  1 [*EOS ]          c3
  0 [*--- ]          c4
  0 [*--- ]          c5
  0 [*--- ]          c6
  0 [*--- ]       c1
  0 [*--- ]       c2
  1 [*EOS ] [main: added end-of-stream marker]
  1 [*EOS ]    c3
  0 [*--- ]    c4
  0 [*--- ]    c5
  0 [*--- ]    c6
  1 [*EOS ] [main: added end-of-stream marker]
  1 [*EOS ]       c3
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6

Consumer consumption:
  C0 -> 0
  C1 -> 10
  C2 -> 0

Total time: 12.06 seconds

4.题8

main-one-cv-while.c中do_get和do_fill与main-two-cv-while.c中的相同,消费者与生产者的部分只使用了一个条件变量。

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
void *producer(void *arg) {
    
    
    int id = (int) arg;
    // make sure each producer produces unique values
    int base = id * loops; 
    int i;
    for (i = 0; i < loops; i++) {
    
       p0;
		Mutex_lock(&m);             p1;
		while (num_full == max) {
    
       p2;
	    	Cond_wait(&cv, &m);     p3;
		}
		do_fill(base + i);          p4;
		Cond_signal(&cv);           p5;
		Mutex_unlock(&m);           p6;
    }
    return NULL;
}
                                                                               
void *consumer(void *arg) {
    
    
    int id = (int) arg;
    int tmp = 0;
    int consumed_count = 0;
    while (tmp != END_OF_STREAM) {
    
     c0;
		Mutex_lock(&m);            c1;
		while (num_full == 0) {
    
        c2;
	    	Cond_wait(&cv, &m);    c3;
        }
		tmp = do_get();            c4;
		Cond_signal(&cv);          c5;
		Mutex_unlock(&m);          c6;
		consumed_count++;
    }

在对缓冲区进行写和读取的部分有锁进行保护,而条件变量的使用保证了缓冲区有数据才会读取,无数据才会写入,当只有一个生产者和一个消费者时,这段代码是可以正确运行的,不会出现错误。

5.题9

main-one-cv-while.c的代码中消费者与生产者只使用了一个条件变量来进行等待,当有多个消费者时,就会产生问题。有两个消费者,一个生产者时,假设生产者先运行,然后唤醒一个消费者并进入睡眠,消费者消费值后需要唤醒一个线程,如果此时唤醒的是另一个消费者,另一个消费者醒来后无值可消费,就会睡眠,造成了三个线程都进入睡眠的情况。以上是书中给出的出错情况,由于程序添加了调度,难以复现这种错误。所以考虑另一种错误,让生产者先生产填满缓冲区后进入睡眠,一个消费者消费该值,然后唤醒另一个消费者,两个消费者都没有值可消费,睡眠等待。 ./main-one-cv-while -p 1 -c 2 -m 1 -P 0,0,0,0,0,0,1 -l 3 -v -t,运行后,生产者完成一次生产就休眠,最后一次时,C1消费后唤醒了C2,两个消费者都进入休眠,,将无法再唤醒。

NF        P0 C0 C1 
  0 [*--- ] p0
  0 [*--- ]       c0
  0 [*--- ]    c0
  0 [*--- ] p1
  1 [*  0 ] p4
  1 [*  0 ] p5
  1 [*  0 ] p6
  1 [*  0 ]       c1
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c1
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ]       c1
  0 [*--- ]       c2
  0 [*--- ] p0
  0 [*--- ] p1
  1 [*  1 ] p4
  1 [*  1 ] p5
  1 [*  1 ] p6
  1 [*  1 ]    c3
  0 [*--- ]    c4
  0 [*--- ]    c5
  0 [*--- ]    c6
  0 [*--- ]       c3
  0 [*--- ]       c2
  0 [*--- ]    c0
  0 [*--- ]    c1
  0 [*--- ]    c2
  0 [*--- ] p0
  0 [*--- ] p1
  1 [*  2 ] p4
  1 [*  2 ] p5
  1 [*  2 ] p6
  1 [*  2 ]       c3
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3
  0 [*--- ]       c0
  0 [*--- ]    c2
  0 [*--- ]       c1
  0 [*--- ]       c2
  1 [*EOS ] [main: added end-of-stream marker]
  1 [*EOS ]    c3
  0 [*--- ]    c4
  0 [*--- ]    c5
  0 [*--- ]    c6
  0 [*--- ]       c3
  0 [*--- ]       c2

6.题10

在main-two-cv-if.c中,判断缓冲区是否为空使用if语句

void *producer(void *arg) {
    
    
    int id = (int) arg;
    // make sure each producer produces unique values
    int base = id * loops; 
    int i;
    for (i = 0; i < loops; i++) {
    
       p0;
	Mutex_lock(&m);             p1;
	if (num_full == max) {
    
          p2;
	    Cond_wait(&empty, &m);  p3;
	}
	do_fill(base + i);          p4;
	Cond_signal(&fill);         p5;
	Mutex_unlock(&m);           p6;
    }
    return NULL;
}
                                                                               
void *consumer(void *arg) {
    
    
    int id = (int) arg;
    int tmp = 0;
    int consumed_count = 0;
    while (tmp != END_OF_STREAM) {
    
     c0;
	Mutex_lock(&m);            c1;
	if (num_full == 0) {
    
           c2;
	    Cond_wait(&fill, &m);  c3;
        }
	tmp = do_get();            c4;
	Cond_signal(&empty);       c5;
	Mutex_unlock(&m);          c6;
	consumed_count++;
    }

    // return consumer_count-1 because END_OF_STREAM does not count
    return (void *) (long long) (consumed_count - 1);
}

当只有一个生产者和一个消费者时,如果消费者判断缓冲区为空,休眠等待,生产者生产后唤醒消费者,消费者继续执行c4进行消费,不会产生问题。当有一个生产者和两个消费者时就会产生问题,消费者休眠后,生产者生产唤醒了该消费者,如果没来得及唤醒并上锁,另一个消费者就进入消费了该值,这个消费者唤醒后执行c4就会发生错误。只要消费者c1在c0处,而生产者刚完成生产,就可能产生这种问题,不需要特别设定睡眠,./main-two-cvs-if -m 1 -c 2 -p 1 -l 10 -v 运行程序,一次该错误发生的情况如下:

 NF        P0 C0 C1 
  0 [*--- ] p0
  0 [*--- ]       c0
  0 [*--- ]    c0
  0 [*--- ]       c1
  0 [*--- ]       c2
  0 [*--- ]    c1
  0 [*--- ]    c2
  0 [*--- ] p1
  1 [*  0 ] p4
  1 [*  0 ] p5
  1 [*  0 ] p6
  1 [*  0 ]       c3
  1 [*  0 ] p0
  0 [*--- ]       c4
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ] p1
  0 [*--- ]       c0			//消费者C1已经在c0处
  1 [*  1 ] p4
  1 [*  1 ] p5
  1 [*  1 ] p6					//生产结束,唤醒c0,但是消费者C1先运行了
  1 [*  1 ]       c1
  1 [*  1 ] p0
  0 [*--- ]       c4			//C1抢先消费了值
  0 [*--- ]       c5
  0 [*--- ]       c6
  0 [*--- ]    c3				//C0准备消费,将发生错误
  0 [*--- ]       c0
error: tried to get an empty buffer

7.题11

在main-cvs-while-extra-unlock.c中,条件变量处都添加了锁,但缓冲区相关的do_fill和do_get处没有加锁,消费者生产者都可能同时访问,会产生错误。

void *producer(void *arg) {
    
    
    int id = (int) arg;
    // make sure each producer produces unique values
    int base = id * loops; 
    int i;
    for (i = 0; i < loops; i++) {
    
       p0;
	Mutex_lock(&m);             p1;
	while (num_full == max) {
    
       p2;
	    Cond_wait(&empty, &m);  p3;
	}
	Mutex_unlock(&m);
	do_fill(base + i);          p4;
	Mutex_lock(&m);
	Cond_signal(&fill);         p5;
	Mutex_unlock(&m);           p6;
    }
    return NULL;
}
                                                                               
void *consumer(void *arg) {
    
    
    int id = (int) arg;
    int tmp = 0;
    int consumed_count = 0;
    while (tmp != END_OF_STREAM) {
    
     c0;
	Mutex_lock(&m);            c1;
	while (num_full == 0) {
    
        c2;
	    Cond_wait(&fill, &m);  c3;
        }
	Mutex_unlock(&m);
	tmp = do_get();            c4;
	Mutex_lock(&m);
	Cond_signal(&empty);       c5;
	Mutex_unlock(&m);          c6;
	consumed_count++;
    }

    // return consumer_count-1 because END_OF_STREAM does not count
    return (void *) (long long) (consumed_count - 1);
}

具体而言,如果两个生产者都在执行do_fill,对同一个缓冲区进行操作,对buf[fill_ptr]赋值value,还没有进行fill_ptr+1,此时另一个生产者调度运行,对同一个buf[fill_ptr]赋值value,就导致对同一个缓冲区两次赋值,第一次的赋值被覆盖掉而没有被消费。题目中给出的程序总是单步完成p4,调度没有在单条指令执行时发生,使用指定睡眠也没有出现生产者执行p4时调度到另一个生产者执行p4,似乎不会出现这个错误。

第31章

1.题1

根据要求,需要完成fork-join.c中的代码。该段代码是将信号量作为条件变量使用的实例,父进程创建子线程后,等待子线程的完成。只需要使用信号量,让父进程在创建完子进程后调用sem_wait等待,子线程执行完成后调用sem_post唤醒父进程。信号量的初始值应该设为0,如果父线程先运行,则将信号量-1,睡眠等待,子线程运行结束后将信号量加为0,父线程运行;如果子线程先运行,父线程运行时信号量已被加为1,就可以直接运行。补全fork-join.c中的代码:

sem_t s; 

void *child(void *arg) {
    
    
    printf("child\n");
    // use semaphore here
    sem_post(&s);
    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    pthread_t p;
    printf("parent: begin\n");
    // init semaphore here
    sem_init(&s,0,0);
    Pthread_create(&p, NULL, child, NULL);
    // use semaphore here
    sem_wait(&s);
    printf("parent: end\n");
    return 0;
}

运行结果正常,父线程等待子线程运行结束后继续执行。

parent: begin
child
parent: end

2.题2

第二题需要完成的是聚集问题:有两个线程,每个线程的代码都要进入聚集部分,一个线程在另一个线程进入该段代码之前不能结束。

void *child_1(void *arg) {
    
    
    printf("child 1: before\n");
    // what goes here?
    printf("child 1: after\n");
    return NULL;
}

void *child_2(void *arg) {
    
    
    printf("child 2: before\n");
    // what goes here?
    printf("child 2: after\n");
    return NULL;
}

在before与after之间就是两个线程聚集的部分,因此正确的解决这个问题后,应该在两个子线程都输出before后才输出after。可以使用两个信号量来实现,此处的聚集本质上是一种相互的等待,两个线程都需要等到对方进入聚集处,才可以继续执行。使用信号量s1和s2,child1调用sem_post将s1信号量+1,然后调用sem_wait等待child2,表示自己已进入聚集处,child2同理。两个信号量都用作了条件变量,与上题相同,信号量应该初始化为0。

sem_t s1, s2;

void *child_1(void *arg) {
    
    
    printf("child 1: before\n");
    sem_post(&s1);
    sem_wait(&s2);
    printf("child 1: after\n");
    return NULL;
}

void *child_2(void *arg) {
    
    
    printf("child 2: before\n");
    sem_post(&s2);
    sem_wait(&s1);
    printf("child 2: after\n");
    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    pthread_t p1, p2;
    printf("parent: begin\n");
    // init semaphores here
    sem_init(&s1,0,0);
    sem_init(&s2,0,0);
    Pthread_create(&p1, NULL, child_1, NULL);
    Pthread_create(&p2, NULL, child_2, NULL);
    Pthread_join(p1, NULL);
    Pthread_join(p2, NULL);
    printf("parent: end\n");
    return 0;
}

运行程序,输出正确。

parent: begin
child 2: before
child 1: before
child 1: after
child 2: after
parent: end

3.题4

本题需要使用信号量完成读者写者锁。在reader-writer.c中定义了读者与写者,读者和写者分别进行loops次读和写,写者每次将value值+1,可以通过参数指定读者与写者的数量。而读者与写者对数据的访问需要使用的锁即rwlock_t,就是需要实现的锁吗,需要完成该锁读者写者锁的获取以及相应的释放。

int loops;
int value = 0;
rwlock_t lock;

void *reader(void *arg) {
    
    
    int i;
    for (i = 0; i < loops; i++) {
    
    
		rwlock_acquire_readlock(&lock);
		printf("read %d\n", value);
		rwlock_release_readlock(&lock);
    }
    return NULL;
}

void *writer(void *arg) {
    
    
    int i;
    for (i = 0; i < loops; i++) {
    
    
		rwlock_acquire_writelock(&lock);
		value++;
		printf("write %d\n", value);
		rwlock_release_writelock(&lock);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    assert(argc == 4);
    int num_readers = atoi(argv[1]);
    int num_writers = atoi(argv[2]);
    loops = atoi(argv[3]);
    pthread_t pr[num_readers], pw[num_writers];
    rwlock_init(&lock);
    printf("begin\n");

    int i;
    for (i = 0; i < num_readers; i++)
	Pthread_create(&pr[i], NULL, reader, NULL);
    for (i = 0; i < num_writers; i++)
	Pthread_create(&pw[i], NULL, writer, NULL);
    for (i = 0; i < num_readers; i++)
	Pthread_join(pr[i], NULL);
    for (i = 0; i < num_writers; i++)
	Pthread_join(pw[i], NULL);
    
    printf("end: value %d\n", value);
    return 0;
}

首先对rwlock_t结构进行定义,该结构中需要包含一个基本的二值信号量锁,还需要一个写者锁,保证只有一个写者可以进行写,最后还需要一个整型变量记录读者数量,第一个读者会获取写锁,其他读者可以读,只有所有读者都结束,读者数为0才可以有写者获取锁。

typedef struct __rwlock_t {
    
    
	sem_t lock;
	sem_t writelock;
	int readers;
} rwlock_t;

由于该问题中,信号量做锁使用,将信号量初始化为1。读者数初始化为0。

void rwlock_init(rwlock_t *rw) {
    
    
	sem_init(&rw->lock,0,1);
	sem_init(&rw->writelock,0,1);
	rw->readers=0;
}

同一时刻可以有多个读者读取数据,因此获取读者锁中,只有第一个读者需要同时获取写者锁,其他读者只需要将读者数+1,释放读者锁同理。

void rwlock_acquire_readlock(rwlock_t *rw) {
    
    
	sem_wait(&rw->lock);
	rw->readers++;
	if(rw->readers==0) sem_wait(&rw->writelock);
	sem_post(&rw->lock);
}

void rwlock_release_readlock(rwlock_t *rw) {
    
    
	sem_wait(&rw->lock);
	rw->readers--;
	if(rw->readers==0) sem_post(&rw->writelock);
	sem_post(&rw->lock);
}

写者锁的获取与释放只需要调用sem_wait和sem_post。

void rwlock_acquire_writelock(rwlock_t *rw) {
    
    
	sem_wait(&rw->writelock);
}

void rwlock_release_writelock(rwlock_t *rw) {
    
    
	sem_post(&rw->writelock);
}

运行程序,设置2个读者,2个写者,loops=2,先有一个读者进行了读,此后2个写者进行了写,最后一个读者再次读,运行情况是正常的。

y@ubuntu:~/workspace/threads-sema$ ./reader-writer 2 2 2
begin
read 0
read 0
write 1
write 2
write 3
write 4
read 4
read 4
end: value 4

由于写者总是需要等待所有读者都结束才可以获取写锁,当读者过多时,写者就可能会饿死。运行一个有100个读者,1个写者情况,可以看到一直是读者进行读,直到最后写者才获取写锁进行写,这种情况写者就很可能饿死。

4.题5

本题需要解决读者写者锁中写者可能饿死的问题。写者可能饿死,是因为读者数量不受限制,同一时刻可以有多个读者进行读,而只要有读者,写者就不能获取写锁。为了解决这个问题,可以再增加一个信号量实现一个锁writerlock,一旦有写者准备进行写操作,尝试获取该锁,获取该锁后可以使新的读者不能进行读,直到写者完成写。这样读者的数量就受到了限制,写者能够保证在等待当前数量的读者读取数据后,可以进行写操作,而不需要因为一直有新加入的读者而等待至饿死。

typedef struct __rwlock_t {
    
    
	sem_t lock;
	sem_t writelock;
	sem_t writerlock;
	int readers;
} rwlock_t;


void rwlock_init(rwlock_t *rw) {
    
    
	sem_init(&rw->lock,0,1);
	sem_init(&rw->writelock,0,1);
	sem_init(&rw->writerlock,0,1);
	rw->readers=0;
	
}

void rwlock_acquire_readlock(rwlock_t *rw) {
    
    
	sem_wait(&rw->writerlock);
	sem_wait(&rw->lock);
	rw->readers++;
	if(rw->readers==0) sem_wait(&rw->writelock);
	sem_post(&rw->lock);
	sem_post(&rw->writerlock);
}

void rwlock_release_readlock(rwlock_t *rw) {
    
    
	sem_wait(&rw->lock);
	rw->readers--;
	if(rw->readers==0) sem_post(&rw->writelock);
	sem_post(&rw->lock);
}

void rwlock_acquire_writelock(rwlock_t *rw) {
    
    
	sem_wait(&rw->writerlock);
	sem_wait(&rw->writelock);
}

void rwlock_release_writelock(rwlock_t *rw) {
    
    
	sem_post(&rw->writelock);
	sem_post(&rw->writerlock);
}

运行以上程序,运行一个有100个读者,1个写者的情况,loop=3,与上一题中同样的情况相比,明显饥饿问题得到了缓解,写的操作不再总是在最后才进行。多次运行,有的情况下写操作还是很靠后,这可能是因为在写者获取写者锁writerlock阻止读者读之前,已经有很多读者在进行读操作了。

5.题6

本题要求实现一种不会产生饥饿的锁,任何尝试获取该锁的线程都最终可以获取该锁而不会被饿死。线程无法获取锁而饿死的原因是当线程等待时,可能有其他线程先获取了锁,不断有其他的线程先抢锁,因此当前线程一直无法拿到锁。如果可以保证当前线程抢锁时,没有其他线程不停插队抢锁,就可以保证当前线程总是可以拿到锁。

具体如何实现没有新的线程插队抢锁,参考书中所提供的资料Little Book of Semaphores中解决该问题的方法,即Morris’s solution。该方法划分出了三个room,三个信号量。一段时间内尝试获取锁的线程先进入room1等待,room1中的线程又会进入room2,并停留在room2中,当room1中不再有线程时,room2到room3的门打开,room1到room2的门关闭,进入room3执行的线程只有每次一个,而room2中等待运行的线程数是确定的,不会有任何一个线程饿死。room2中的线程都执行完毕后,再打开room1的门,让线程进入room1,重复上述过程。使线程能够按批次获取锁并执行,就实现了没有饥饿问题的锁。其中room1和room2中的线程数量是通过变量进行计数的,具体的实现如下:

typedef __ns_mutex_t {
    
    
	sem_t lock;
	sem_t door1;
	sem_t door2;
	int room1;
	int room2;
} ns_mutex_t;

void ns_mutex_init(ns_mutex_t *m) {
    
    
	sem_init(&m->lock,1);
	sem_init(&m->door1,1);
	sem_init(&m->door2,0);
	room1=0;
	room2=0;
}

void ns_mutex_acquire(ns_mutex_t *m) {
    
    
	sem_wait(&m->lock);
	m->room1++;
	sem_post(&m->lock);
	//进入room2
	sem_wait(&m->door1);
	m->room2++;
	sem_wait(&m->lock);
	m->room1--;
	if(m->room1){
    
    				//还有线程进入room1
		sem_post(&m->lock);		//room1room2可进
		sem_post(&m->door1);
	}
	else{
    
    
		sem_post(&m->lock);		//room1空,可进
		sem_post(&m->door2);	//room3可进
	}
	sem_wait(&m->door2);
	m->room2--;
}

void ns_mutex_release(ns_mutex_t *m) {
    
    
	if(m->room2){
    
    
		sem_post(&m->door2);
	}
	else{
    
    
		sem_post(&m->door1);	
	}
}

​仿照其他题目中的程序完成main及worker线程:

int loops;
ns_mutex_t lock;

void *worker(void* arg) {
    
    
    int i;
    for (i = 0; i < loops; i++) {
    
    
		ns_mutex_acquire(&lock);
		ns_mutex_release(&lock);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    assert(argc == 3);
    int num_workers= atoi(argv[1]);
    loops = atoi(argv[2]);
    pthread_t pr[num_workers];
    ns_mutex_init(&lock);
    int i;    
    
    printf("parent: begin\n");
    for (i = 0; i < num_workers; i++)
	Pthread_create(&pr[i], NULL,worker, NULL);
    for (i = 0; i < num_workers; i++)
	Pthread_join(pr[i], NULL);
    printf("parent: end\n");
    return 0;
}

该锁是可以正常工作的,但是最后的实现没有检测是否保证了没有饥饿的情况产生,仅仅是根据参考资料实现了该锁,对这个不会产生饥饿问题的锁的理解还不够深刻,还需要继续学习。

Guess you like

Origin blog.csdn.net/Aaron503/article/details/130660815