进程同步实验 ipcs -*
ipcs -m
linux
中可用命令ipcs -m
观察共享内存 情况
key
共享内存关键值
shmid
共享内存标识
owner
共享内存所由者
perm
共享内存使用权限
byte
共享内存字节数
nattch
共享内存使用计数
status
共享内存状态
ipcs -s
linux
中可用命令ipcs -s
观察信号量数组 的情况
semid
信号量的标识号
nsems
信号量的个数
ipcs -q
linux
中可用命令ipcs -q
观察消息队列 的情况。
msgmid
消息队列的标识号used-bytes
消息的字节长度messages
消息队列中的消息条数ipcrm
在权限允许的情况下您可以使用ipcrm
命令删除系统当前存在的IPC 对象中的任一个对象。
ipcrm -m 21482
: 删除标号为21482 的共享内存。
ipcrm -s 32673
: 删除标号为32673 的信号量数组。
ipcrm -q 18465
: 删除标号为18465 的消息队列。
系统调用 IPC 对象有关的系统调用函数原型都声明在以下的头文件中 :
1 2 #include <sys/types.h> #include <sys/ipc.h>
其余参见指导书
共享内存 创建 创建一段共享内存系统调用语法 :
1 2 #include <sys/shm.h> int shmget (key_t key,int size,int flags) ;
key
共享内存的键值,可以为IPC_PRIVATE,也可以用整数指定一个
size
共享内存字节长度
flags
共享内存权限位。
shmget
调用成功后,如果key
用新整数指定,且flags
中设置了IPC_CREAT
位,则返回一个新建立的共享内存段标识符。 如果指定的key
已存在则返回与key
关联的标识符。 不成功返回-1
附加 令一段共享内存附加到调用进程中的系统调用语法:
1 2 #include <sys/shm.h> char *shmat (int shmid, char *shmaddr,int flags)
shmid
由shmget
创建的共享内存的标识符
shmaddr
总为0,表示用调用者指定的指针指向共享段
flags
共享内存权限位
shmat
调用成功后返回附加的共享内存首地址
分离 令一段共享内存从到调用进程中分离出去的系统调用语法:
1 2 #include <sys/shm.h> int shmdt (char *shmadr) ;
shmadr
进程中指向附加共享内存的指针
shmdt
调用成功将递减附加计数,当计数为0,将删除共享内存。调用不成功返回-1。
信号量 创建 创建一个信号量数组的系统调用有语法:
1 2 #include <sys/sem.h> int semget (key_t key,int nsems, int flags) ;
key
信号量数组的键值,可以为IPC_PRIVATE
,也可以用整数指定一个
nsems
信号量数组中信号量的个数
flags
信号量数组权限位。如果key
用整数指定,应设置IPC_CREAT
位。
semget
调用成功,如果key
用新整数指定,且flags
中设置了IPC_CREAT
位,则返回一个新建立的信号等数组标识符。 如果指定的整数key
已存在则返回与key
关联的标识符。 不成功返回-1
操作 操作信号量数组的系统调用语法:
1 2 #include <sys/sem.h> int semop (int semid,struct sembuf *semop, unsigned nops) ;
semid
由semget
创建的信号量数组的标识符
semop
指向sembuf
数据结构的指针
nops
信号量上的操作数,例如该值为1 相当于P操作,-1 相当于V操作.
semop
调用成功返回0,不成功返回-1。
控制 控制信号量数组的系统调用语法:
1 2 #include <sys/sem.h> int semctl (int semid,int semnum,int cmd, union semun arg) ;
semid
由semget
创建的信号量数组的标识符
semnum
该信号量数组中的第几个信号量
cmd
对信号量发出的控制命令。例如:
GETVAL
返回当前信号量状态
SETVAL
设置信号量状态
IPC_RMD
删除标号为semid
的信号量
arg
保存信号量状态的联合体,信号量的值是其中一个基本成员
1 2 3 4 union semun {int val; ...... };
semctl
执行不成功返回-1,否则返回指定的cmd
的值。
semget() semget()
函数既可以用于获取之前创建的信号量集合,也可以用于创建新的信号量集合。semget()
的函数原型如下:
1 2 int semget (key_t key, int nsems, int semflg) ;
key
是绑定在信号量集合上的,我们可以用key
来寻找已经创建的信号量集合,或者用于创建并绑定新的信号量集合。注意,key
和函数的返回值(信号量集合)不是一样的。nsems
表示该信号量集合有多少个信号量,如果用信号量来创建binary semaphore
,我们只需要将nsems
设置为1。semflg
是设置信号量集合的标志位,其中最低9位是信号量集合的访问权限(和文件一样,3个8进制数字,比如0777)。其他位和文件创建的标志位类似,比如IPC_CREAT
是创建新的信号量集合(文件是O_CREAT
),在使用信号量前,我们都需要创建信号量集合。
在调用该系统调用后,与信号量集合绑定的数据结构semid_ds
也被初始化。
semop() semop()
用于操作信号量,简单来说就是对信号量的计数器进行加减。函数原型如下:
1 int semop (int semid, struct sembuf *sops, size_t nsops) ;
semid
是信号量集合的标识符,注意,这不是semget()
中的key
而是这个函数的返回值。nsops
是有多少个信号量需要被操作,在我们的例子中只有1个信号量需要被操作。sops
是对信号量操作的具体命令,它是一个struct sembuf
类型的结构体,一共有nsops
个这样的结构体:
1 2 3 unsigned short sem_num; short sem_op; short sem_flg;
sem_num
是从0开始计数的,表示是第几个信号量;sem_flg
可以选择IPC_NOWAIT
和SEM_UNDO
,SEM_UNDO
表示在进程结束后,内核会自动释放没有主动释放的信号量。sops
是按照数组的顺序执行的,并且是原子的,如果所有的信号量不能同时操作,那么就不进行操作。
sem_op
的使用很简单,但具体如何设置、不同设置有什么不同却比较麻烦。在binary semaphore
的例子中,sem_op
设置为1就是信号量计数器加1;设置为-1就是信号量计数器减1,下面具体讲讲sem_op
设置背后的故事。
除了上面提到的sembuf
结构体,每个信号量还对应一系列变量:
1 2 3 4 unsigned short semval; unsigned short semzcnt; unsigned short semncnt; pid_t sempid;
之前提到的信号量计数器就是semval
,semzcnt
是等待semval
为0的进程数,semncnt
是等待semval
增加的进程数。semop()
对信号量的操作都会改变这些变量。
如果sem_op
设置为正数,那么每次操作后semval
将变成semval + sem_op
;如果sem_flg
设置了SEM_UNDO
,那么semadj
将变成semadj - sem_op
。进程需要有写的权限才能修改信号量。
如果sem_op
设置为负数,情况要复杂些:
如果abs(sem_op)
小于等于semval
,那么semval
将变成semval - abs(sem_op)
;如果sem_flg
设置了SEM_UNDO
,那么semadj
将变成semadj + abs(sem_op)
; 如果abs(sem_op)
大于semval
并且sem_flg
设置了IPC_NOWAIT
,那么将返回错误码EAGAIN
,并且sem_op
的操作不会进行; 如果2中没有设置IPC_NOWAIT
,那么semval
将变成semval - abs(sem_op)
,同时semncnt
变成semncnt + 1
,并且进程会进入sleep
状态。当semval >= abs(sem_op)
时,进程会被唤醒。 其他具体细节请参考man semop
。
从加锁和解锁的角度进行思考,当semval = 0
时,如果操作是sem_op = -1
,那么semval
将变成-1,因此进程进入睡眠状态:这就是加锁的操作;同理,sem_op = 1
是解锁的操作。
semctl() 通过上面两个系统调用,我们知道如何创建信号量以及如何操作信号量,下面这个系统调用可以初始化信号量以及删除信号量。我们先来看看semctl()
的函数原型:
1 int semctl (int semid, int semnum, int cmd, ...) ;
semctl()
支持的命令有很多,这里我们主要介绍两个 - IPC_RMID
和SETVAL
。semid
是信号量集合的索引,semnum
是其中第几个信号量(从0开始),cmd
就是命令。这个函数是可变长度参数,有些命令是4个参数,第4个参数是union semun
,这个union
定义如下,注释中注明了哪些命令要用这个union
:
1 2 3 4 5 6 union semun { int val; struct semid_ds *buf ; unsigned short *array ; struct seminfo *__buf ; };
使用SETVAL
命令时,我们就是通过val
给semval
赋值,用于初始化信号量。我们这里看看如何使用semctl()
来初始化和删除信号量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int set_sem (int sem_id) { union semun sem_union ; sem_union.val = 1 ; if (semctl(sem_id, 0 , SETVAL, sem_union) == -1 ) { fprintf (stderr , "Failed to set sem\n" ); return 0 ; } return 1 ; } void del_sem (int sem_id) { union semun sem_union ; if (semctl(sem_id, 0 , IPC_RMID, sem_union) == -1 ) fprintf (stderr , "Failed to delete sem, sem has been del.\n" ); }
参考:https://rdou.github.io/2020/06/22/Linux编程基础3-进程间通信-信号量/
基本逻辑 生产者:首先如果缓冲区满则生产者阻塞,其次生产者不能同时进行提供材料,所以要设置互斥锁使其不能同时进行执行。生产者每次提供其中的两种后,唤醒抽烟者。
抽烟者:而对于抽烟者来说,刚开始并没有所需要的材料,要等待生产者的唤醒,同时还要设置互斥锁防止抽烟者同时进行吸烟。判断当前放的两个材料是否是其所需,是则从共享缓冲区间读取所需要的数据,然后唤起两个生产者和其他抽烟者。若不是,则唤醒其他抽烟者抽烟。
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 semaphore tobacco_paper = 0 ; semaphore tobacco_matches = 0 ; semaphore paper_matches = 0 ; semaphore doneSomking = 0 ; while ( true ) { pick a random number from 1 -3 if random number is 1 signal( tobacco_paper ) else if random number is 2 signal( tobacco_matches ) else if random number is 3 signal( paper_matches ) wait( doneSmoking ) } while (true ){ wait( tobacco_paper ); signal( doneSmoking ); } while (true ){ wait(tobacco_matches); signal( doneSmoking ); } while (true ){ wait(matches_paper ); signal( doneSmoking ); }
Linux下的代码设计 参考
①关系分析:供应者与三个抽烟者分别是同步关系。由于抽烟者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)。
②整体思路:一共设计六个文件,ipc.h
和ipc.c
设计信号量机制实现的底层逻辑,例如消息队列、P操作、V操作等。设计四个进程,供应者作为生产者向三个抽烟者提供材料。
③信号量设置:信号量offer1、offer2、offer3分别表示烟草和纸组合,烟草和胶水组合,胶水和纸组合。信号量finish表示抽烟完成信号。它们之间的协同关系如下图:
同时还要设置一个互斥信号量 ,保证4个进程互斥访问缓冲区,但该互斥信号量不是必须的,因为本例中缓冲区大小只为1。
代码 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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/msg.h> #define BUFSZ 256 int get_ipc_id (char *proc_file,key_t key) ;char *set_shm (key_t shm_key,int shm_num,int shm_flag) ;int set_msq (key_t msq_key,int msq_flag) ;int set_sem (key_t sem_key,int sem_val,int sem_flag) ;int down (int sem_id) ;int up (int sem_id) ;typedef union semuns { int val; } Sem_uns; typedef struct msgbuf { long mtype; char mtext[1 ]; } Msg_buf; key_t buff_key;int buff_num;char *buff_ptr;key_t pput_key;int pput_num;int *pput_ptr;key_t cget_key;int cget_num;int *cget_ptr;key_t offer1_key;key_t offer2_key;key_t offer3_key;key_t finish_key;key_t mutex_key;int offer1;int offer2;int offer3;int finish;int mutex;int sem_val;int sem_flg;int shm_flg;
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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 #include "ipc.h" int get_ipc_id (char *proc_file, key_t key) { FILE *pf; int i,j; char line[BUFSZ], colum[BUFSZ]; if ((pf = fopen(proc_file,"r" )) == NULL ) { perror("Proc file not open" ); exit (EXIT_FAILURE); } fgets(line, BUFSZ, pf); while (!feof(pf)) { i = j = 0 ; fgets(line, BUFSZ,pf); while (line[i] == ' ' ) i++; while (line[i] != ' ' ) colum[j++] = line[i++]; colum[j] = '\0' ; if (atoi(colum) != key) continue ; j = 0 ; while (line[i] == ' ' ) i++; while (line[i] !=' ' ) colum[j++] = line[i++]; colum[j] = '\0' ; i = atoi(colum); fclose(pf); return i; } fclose(pf); return -1 ; } int down (int sem_id) { struct sembuf buf ; buf.sem_num = 0 ; buf.sem_op = -1 ; buf.sem_flg = SEM_UNDO; if ((semop(sem_id,&buf,1 )) < 0 ) { perror("P error " ); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } int up (int sem_id) { struct sembuf buf ; buf.sem_op = 1 ; buf.sem_num = 0 ; buf.sem_flg = SEM_UNDO; if ((semop(sem_id,&buf,1 )) < 0 ) { perror("V error " ); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } int set_sem (key_t sem_key, int sem_val, int sem_flg) { int sem_id; Sem_uns sem_arg; if ((sem_id = get_ipc_id("/proc/sysvipc/sem" , sem_key)) < 0 ) { if ((sem_id = semget(sem_key, 1 , sem_flg)) < 0 ) { perror("semaphore create error" ); exit (EXIT_FAILURE); } sem_arg.val = sem_val; if (semctl(sem_id, 0 , SETVAL, sem_arg) < 0 ) { perror("semaphore set error" ); exit (EXIT_FAILURE); } } return sem_id; } char * set_shm (key_t shm_key, int shm_num, int shm_flg) { int i, shm_id; char * shm_buf; if ((shm_id = get_ipc_id("/proc/sysvipc/shm" , shm_key)) < 0 ) { if ((shm_id = shmget(shm_key, shm_num, shm_flg)) < 0 ) { perror("shareMemory set error" ); exit (EXIT_FAILURE); } if ((shm_buf = (char *)shmat(shm_id, 0 , 0 )) < (char *)0 ) { perror("get shareMemory error" ); exit (EXIT_FAILURE); } for (i = 0 ; i < shm_num; i++) shm_buf[i] = 0 ; } if ((shm_buf = (char *)shmat(shm_id, 0 , 0 )) < (char *)0 ) { perror("get shareMemory error" ); exit (EXIT_FAILURE); } return shm_buf; } int set_msq (key_t msq_key,int msq_flg) { int msq_id; if ((msq_id = get_ipc_id("/proc/sysvipc/msg" , msq_key)) < 0 ) { if ((msq_id = msgget(msq_key,msq_flg)) < 0 ) { perror("messageQueue set error" ); exit (EXIT_FAILURE); } } return msq_id; }
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 #include "ipc.h" #include <stdlib.h> int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 1 ; buff_key = 101 ; buff_num = 1 ; pput_key = 102 ; pput_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); pput_ptr = (int *)set_shm(pput_key, pput_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer1 = set_sem(offer1_key, sem_val, sem_flg); offer2 = set_sem(offer2_key, sem_val, sem_flg); offer3 = set_sem(offer3_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); int i = 0 ; while (1 ) { i = (i + 1 ) % 3 ; buff_ptr[*pput_ptr] = i + 1 ; sleep(rate); down(mutex); *pput_ptr = (*pput_ptr + 1 ) % buff_num; if (i == 0 ) { printf ("%d put offer1 - tobacco and paper into buffer[%d]\n" , getpid(), *pput_ptr); up(offer1); } else if (i == 1 ) { printf ("%d put offer2 - glue and tobacco into buffer[%d]\n" , getpid(), *pput_ptr); up(offer2); } else { printf ("%d put offer3 - glue and paper into buffer[%d]\n" , getpid(), *pput_ptr); up(offer3); } up(mutex); down(finish); } return EXIT_SUCCESS; }
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 #include "ipc.h" int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 3 ; buff_key = 101 ; buff_num = 1 ; cget_key = 103 ; cget_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); cget_ptr = (int *)set_shm(cget_key, cget_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer3 = set_sem(offer3_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); while (1 ) { sleep(rate); down(offer3); down(mutex); *cget_ptr = (*cget_ptr + 1 ) % buff_num; printf ("%d smoker get glue and paper offer%d from buffer[%d]\n" , getpid(), buff_ptr[*cget_ptr], *cget_ptr); up(mutex); up(finish); } return EXIT_SUCCESS; }
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 #include "ipc.h" int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 3 ; buff_key = 101 ; buff_num = 1 ; cget_key = 103 ; cget_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); cget_ptr = (int *)set_shm(cget_key, cget_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer2 = set_sem(offer2_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); while (1 ) { sleep(rate); down(offer2); down(mutex); *cget_ptr = (*cget_ptr + 1 ) % buff_num; printf ("%d smoker get glue and tobacco offer%d from buffer[%d]\n" , getpid(), buff_ptr[*cget_ptr], *cget_ptr); up(mutex); up(finish); } return EXIT_SUCCESS; }
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 #include "ipc.h" int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 3 ; buff_key = 101 ; buff_num = 1 ; cget_key = 103 ; cget_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); cget_ptr = (int *)set_shm(cget_key, cget_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer1 = set_sem(offer1_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); while (1 ) { sleep(rate); down(offer1); down(mutex); *cget_ptr = (*cget_ptr + 1 ) % buff_num; printf ("%d smoker get tobacco and paper offer%d from buffer[%d]\n" , getpid(), buff_ptr[*cget_ptr], *cget_ptr); up(mutex); up(finish); } return EXIT_SUCCESS; }
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 hdrs = ipc.h opts = -g -c p_src = producer.c ipc.c p_obj = producer.o ipc.o c_T_src = consumer_tobacco.c ipc.c c_T_obj = consumer_tobacco.o ipc.o c_P_src = consumer_paper.c ipc.c c_P_obj = consumer_paper.o ipc.o c_G_src = consumer_glue.c ipc.c c_G_obj = consumer_glue.o ipc.o all: producer consumer_tobacco consumer_paper consumer_glue producer: $(p_obj) gcc $(p_obj) -o producer producer.o: $(p_src) $(hdrs) gcc $(opts) $(p_src) consumer_tobacco: $(c_T_obj) gcc $(c_T_obj) -o consumer_tobacco consumer_tobacco.o: $(c_T_src) $(hdrs) gcc $(opts) $(c_T_src) consumer_paper: $(c_P_obj) gcc $(c_P_obj) -o consumer_paper consumer_paper.o: $(c_P_src) $(hdrs) gcc $(opts) $(c_P_src) consumer_glue: $(c_G_obj) gcc $(c_G_obj) -o consumer_glue consumer_glue.o: $(c_G_src) $(hdrs) gcc $(opts) $(c_G_src) clean: rm producer consumer_tobacco consumer_paper consumer_glue *.o
一些问题 由于该程序会修改文件/proc/sysvipc/sem
中的记录,且运行完后不会删除,因此如果多次对文件进行修改,并make
后执行,可能使用到一个错误的信号量id,对该信号量的操作不会反映到程序中,程序会直接跳过对信号量的P操作和V操作。解决方法是重启虚拟机。 比如,就会这样:
finish
同步信号量的设置可以为1或0,如果是1,则应该在while
循环开始处调用P操作,这样第一遍循环可以顺利执行,阻塞在第二次循坏的开始处;如果是0,则阻塞在while
循坏的尾部。