多进程编程的核心技术是进程间的同步——通信与互斥访问
一、进程间的通信
1、管道
2、System V信号量
3、共享内存
4、消息队列
5、信号
6、套接字
二、进程间对资源的互斥访问
条件变量
信号量
读写锁(记录锁)
自旋锁
原子锁(顺序锁)
记录锁:
int fcntl(int fd, int cmd, struct flock* flockptr);
cmd可以是F_GETLK、F_SETLK或F_SETLKW。
F_GETLK:查看是否有其它进程锁与flockptr所描述的冲突,如果存在冲突,则将其信息写到flockptr。反之只改写flockptr.l_type为F_UNLCK。
F_SETLK:设置或清楚flockptr描述的锁。若跟已有的锁冲突,则返回-1并将errno设置为EACCES或者EAGAIN。
F_SETLKW:作用同上。但若有冲突的锁,则阻塞等待直到与其它锁冲突消失或被信号中断。被信号中断返回时,返回-1并设置errno为EINTR。
flock结构体定义:
struct flock { short int l_type; //F_RDLCK共享性读锁定,F_WRLCK独占性写锁定和F_UNLCK释放锁定 short int l_whence; // SEEK_SET 文件头 SEEK_CUR当前位置 SEEK_END文件末尾 __off_t l_start; __off_t l_len; __pid_t l_pid; };
例如给某个文件上写锁:
flock lk; lk.l_type = F_WRLCK; lk.l_whence = SEEK_SET; lk.l_start = 0; lk.l_len = 0; fcntl(fd,F_SETLK,&lk);
理解:
1、当文件无锁的时候,任何进程都能上读锁或写锁。
2、当文件被A进程上了读锁的时候,其它任何进程都可以上读锁,但不能上写锁!(很容易理解,A进程上了读锁(需要读此文件),此时其它进程也上读锁不会对A做成影响。但如果其它进程要上写锁(需要对文件进行写操作),那会影响到A的进行,因为写操作会修改原文件!!)
3、当文件被A进程上了写锁,其它任何进程都不能上写或读锁。(因为A进程上写锁(代表需要写此文件),如果此时其它进程读此文件的话必定受影响,其它进程写操作的话,双方都受影响。)
内核中理解记录锁:
在系统在每个文件vNode中(每个打开文件都有一个V节点结构)有一个记录锁链表:
链表中元素有四个字段:
1、锁的flag。
2、锁的起始偏移量(绝对偏移量)。
3、锁的长度(为0则代表直到文件最后——跟随文件大小而改变)。
4、进程ID号。
注意第4个字段,由于文件锁参数是记录在系统V节点表里的记录锁链表里的,而fork子进程和父进程具有不同的进程ID号,因此子进程自然也不继承父进程的文件锁。同理exec
前后具有相同进程ID号,因此继承相同的文件锁(除非文件标记了close_on_exec,exec之后自动关闭文件,自然同时关闭文件锁)。
强制锁和建议锁:
1、强制锁会影响到其它进程的读写操作:open、read、write等。
例如:A进程对某文件某部分加了强制写锁,其它进程都不能对那部分进行上锁或读写。
2、建议锁只会影响其它进程的上锁操作,而不会限制实际的IO。
例如:A进程对某文件某部分加了强制写锁,其它进程不能对那部分上锁,但还是可以对其读写的(但这样无视锁的做法是不可取的)
注意:在linux中fcntl默认上的是建议锁!!!
协同进程:
建议锁并不强制限制I/O,因此当所有进程都自觉使用建议锁,并根据建议锁的情况考虑是否读写,才能发挥建议锁的作用。这样些遵循建议锁的进程称为协同进程。
记录锁的关闭:
无论用dup复制多少个文件描述符,只要有其中一个被close了,则此进程的所有锁都被关闭了。
记录锁引发的问题:
死锁问题:两个进程互相阻塞等待对方持有的锁,最严重后果是两进程永远阻塞成死锁。例如:A进程有A锁,B进程有B锁,A试图用F_SETLKW设锁到有B锁的资源,同时B也使用F_SETLKW设锁要有A锁的资源,后果是两个傻傻地阻塞下去。(但现在系统会检测死锁,并将出错信息通知其中一个进程)。