Linux 读写锁
程序员文章站
2022-04-10 22:02:11
线程的读写锁函数: 1,读写锁的初始化与销毁,静态初始化的话,可以直接使用PTHREAD_RWLOCK_INITIALIZER。 2,用读的方式加锁和尝试(没锁上就立即返回)加锁。 3,用写的方式加锁和尝试(没锁上就立即返回)加锁。 4,解锁 多个进程在同时读写同一个文件,会发生什么? 例子1:用下 ......
线程的读写锁函数:
1,读写锁的初始化与销毁,静态初始化的话,可以直接使用pthread_rwlock_initializer。
#include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); pthread_rwlock_t rwlock = pthread_rwlock_initializer;
2,用读的方式加锁和尝试(没锁上就立即返回)加锁。
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
3,用写的方式加锁和尝试(没锁上就立即返回)加锁。
#include <pthread.h> int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
4,解锁
#include <pthread.h> int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
多个进程在同时读写同一个文件,会发生什么?
例子1:用下面的例子的执行结果,观察多个进程在同时读写同一个文件,会发生什么。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #define maxline 100 #define fn "num1" void my_lock(int fd){ return; } void my_unlock(int fd){ return; } int main(int args, char** argv){ int fd; long i,seqno; pid_t pid; ssize_t n; char line[maxline + 1]; pid = getpid(); fd = open(fn, o_rdwr, 0664); for(i = 0; i < 20; ++i){ my_lock(fd); lseek(fd, 0l, seek_set); n = read(fd, line, maxline); line[n] = '\0'; seqno = atol(line); printf("%s:pid = %ld, seq = %ld\n", argv[0], (long)pid, seqno); seqno++; snprintf(line, sizeof(line), "%ld\n", seqno); lseek(fd, 0l, seek_set); write(fd, line, strlen(line)); my_unlock(fd); } return 0; }
执行方法:同时执行上面例子的程序2次,也就是2个进程同时读写同一个文件。
ubuntu$ ./flockmain1 & ./flockmain1 &
执行结果如下,发现2个进程同时读写,在①处开始,内核切换进程时,数字乱套了。
ubuntu$ ./flockmain1 & ./flockmain1 & [1] 4760 [2] 4761 ubuntu$ ./flockmain1:pid = 4761, seq = 1 ./flockmain1:pid = 4761, seq = 2 ./flockmain1:pid = 4761, seq = 3 ./flockmain1:pid = 4761, seq = 4 ./flockmain1:pid = 4761, seq = 5 ./flockmain1:pid = 4761, seq = 6 ./flockmain1:pid = 4761, seq = 7 ./flockmain1:pid = 4761, seq = 8 ./flockmain1:pid = 4761, seq = 9 ./flockmain1:pid = 4761, seq = 10 ------------① ./flockmain1:pid = 4760, seq = 10 ./flockmain1:pid = 4761, seq = 11 ./flockmain1:pid = 4761, seq = 12 ./flockmain1:pid = 4761, seq = 13 ./flockmain1:pid = 4761, seq = 14 ./flockmain1:pid = 4761, seq = 15 ./flockmain1:pid = 4761, seq = 16 ./flockmain1:pid = 4761, seq = 17 ./flockmain1:pid = 4761, seq = 18 ./flockmain1:pid = 4761, seq = 19 ./flockmain1:pid = 4761, seq = 20 ./flockmain1:pid = 4760, seq = 11 ./flockmain1:pid = 4760, seq = 12 ./flockmain1:pid = 4760, seq = 13 ./flockmain1:pid = 4760, seq = 14 ./flockmain1:pid = 4760, seq = 15 ./flockmain1:pid = 4760, seq = 16 ./flockmain1:pid = 4760, seq = 17 ./flockmain1:pid = 4760, seq = 18 ./flockmain1:pid = 4760, seq = 19 ./flockmain1:pid = 4760, seq = 20 ./flockmain1:pid = 4760, seq = 21 ./flockmain1:pid = 4760, seq = 22 ./flockmain1:pid = 4760, seq = 23 ./flockmain1:pid = 4760, seq = 24 ./flockmain1:pid = 4760, seq = 25 ./flockmain1:pid = 4760, seq = 26 ./flockmain1:pid = 4760, seq = 27 ./flockmain1:pid = 4760, seq = 28 ./flockmain1:pid = 4760, seq = 29
为了解决上面的问题,必须对文件的内容进行加锁。
如何对文件内容加锁?
使用fcntl函数,它既可以锁整文件,也可以锁文件里的某段内容。通过结构体flock来指定要锁的范围。如果 whence = seek_set;l_start = 0;l_len = 0;就是锁定整个文件。
struct flock { ... short l_type; /* type of lock: f_rdlck, f_wrlck, f_unlck */ short l_whence; /* how to interpret l_start: seek_set, seek_cur, seek_end */ off_t l_start; /* starting offset for lock */ off_t l_len; /* number of bytes to lock */ pid_t l_pid; /* pid of process blocking our lock (set by f_getlk and f_ofd_getlk) */ ... };
- f_setlk:上锁。如果发现已经被别的进程上锁了,就直接返回-1,errno被设置成eacces或者eagain,不阻塞。
- f_setlkw:上锁。阻塞等待。
- f_getlk:得到锁的状态。
修改上面的函数my_lock,my_unlock。main函数不变。
例子2:
void my_lock(int fd){ struct flock lock; lock.l_type = f_wrlck; wlock.l_whence = seek_set; lock.l_start = 0; lock.l_len = 0; fcntl(fd, f_setlkw, lock); } void my_unlock(int fd){ struct flock lock; lock.l_type = f_unlck; lock.l_whence = seek_set; lock.l_start = 0; lock.l_len = 0; fcntl(fd, f_setlk, lock); }
执行结果如下,发现数字不乱套了。
ubuntu$ ./flockmain & ./flockmain & [1] 4882 [2] 4883 ubuntu$ ./flockmain:pid = 4883, seq = 1 ./flockmain:pid = 4883, seq = 2 ./flockmain:pid = 4883, seq = 3 ./flockmain:pid = 4883, seq = 4 ./flockmain:pid = 4883, seq = 5 ./flockmain:pid = 4883, seq = 6 ./flockmain:pid = 4883, seq = 7 ./flockmain:pid = 4883, seq = 8 ./flockmain:pid = 4883, seq = 9 ./flockmain:pid = 4883, seq = 10 ./flockmain:pid = 4883, seq = 11 ./flockmain:pid = 4883, seq = 12 ./flockmain:pid = 4883, seq = 13 ./flockmain:pid = 4883, seq = 14 ./flockmain:pid = 4883, seq = 15 ./flockmain:pid = 4883, seq = 16 ./flockmain:pid = 4883, seq = 17 ./flockmain:pid = 4883, seq = 18 ./flockmain:pid = 4883, seq = 19 ./flockmain:pid = 4883, seq = 20 ./flockmain:pid = 4882, seq = 21 ./flockmain:pid = 4882, seq = 22 ./flockmain:pid = 4882, seq = 23 ./flockmain:pid = 4882, seq = 24 ./flockmain:pid = 4882, seq = 25 ./flockmain:pid = 4882, seq = 26 ./flockmain:pid = 4882, seq = 27 ./flockmain:pid = 4882, seq = 28 ./flockmain:pid = 4882, seq = 29 ./flockmain:pid = 4882, seq = 30 ./flockmain:pid = 4882, seq = 31 ./flockmain:pid = 4882, seq = 32 ./flockmain:pid = 4882, seq = 33 ./flockmain:pid = 4882, seq = 34 ./flockmain:pid = 4882, seq = 35 ./flockmain:pid = 4882, seq = 36 ./flockmain:pid = 4882, seq = 37 ./flockmain:pid = 4882, seq = 38 ./flockmain:pid = 4882, seq = 39 ./flockmain:pid = 4882, seq = 40
到此为止,貌似解决了问题,但是如果同时执行例子1和例子2,结果如下,发现还是乱的。
也就是说在协作线程(cooperating processes)间,文件锁(也叫劝告性上锁)也起作用的。但是不完全不相关的进程中,文件锁也不起作用的。如何解决呢?使用强制性上锁。
ys@ys-virtualbox:~/ipc$ ./flockmain1 & ./flockmain & [1] 3602 [2] 3603 ys@ys-virtualbox:~/ipc$ ./flockmain1:pid = 3602, seq = 1 ./flockmain:pid = 3603, seq = 1 ./flockmain:pid = 3603, seq = 2 ./flockmain:pid = 3603, seq = 3 ./flockmain:pid = 3603, seq = 4 ./flockmain:pid = 3603, seq = 5 ./flockmain:pid = 3603, seq = 6 ./flockmain:pid = 3603, seq = 7 ./flockmain:pid = 3603, seq = 8 ./flockmain:pid = 3603, seq = 9 ./flockmain:pid = 3603, seq = 10 ./flockmain1:pid = 3602, seq = 2 ./flockmain1:pid = 3602, seq = 3 ./flockmain1:pid = 3602, seq = 4 ./flockmain:pid = 3603, seq = 11 ./flockmain:pid = 3603, seq = 12 ./flockmain1:pid = 3602, seq = 5 ./flockmain:pid = 3603, seq = 13 ./flockmain1:pid = 3602, seq = 6 ./flockmain1:pid = 3602, seq = 7 ./flockmain1:pid = 3602, seq = 8 ./flockmain:pid = 3603, seq = 14 ./flockmain:pid = 3603, seq = 15 ./flockmain1:pid = 3602, seq = 9 ./flockmain1:pid = 3602, seq = 10 ./flockmain:pid = 3603, seq = 16 ./flockmain:pid = 3603, seq = 17 ./flockmain1:pid = 3602, seq = 11 ./flockmain:pid = 3603, seq = 18 ./flockmain1:pid = 3602, seq = 12 ./flockmain1:pid = 3602, seq = 13 ./flockmain1:pid = 3602, seq = 14 ./flockmain:pid = 3603, seq = 19 ./flockmain:pid = 3603, seq = 20 ./flockmain1:pid = 3602, seq = 15 ./flockmain1:pid = 3602, seq = 16 ./flockmain1:pid = 3602, seq = 17 ./flockmain1:pid = 3602, seq = 18 ./flockmain1:pid = 3602, seq = 19 ./flockmain1:pid = 3602, seq = 20
第一个问题:假如一个文件被一个进程以读的方式锁定,并有另一个进程在等待读锁定解锁后,用写入的方式锁定,这时是否允许另一个进程的还以读的方式取得锁定?
用例子3来观察:
#include <time.h> #include <sys/time.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> void gftime(char* buf){ struct timeval tv; gettimeofday(&tv, null); long usec = tv.tv_usec; struct tm* tm = localtime(&tv.tv_sec); sprintf(buf, "%d:%d:%d.%ld",tm->tm_hour, tm->tm_min, tm->tm_sec,usec); } int main(){ char buff[100] = {0}; int fd = open("test.dat", o_rdwr | o_creat, 0664); struct flock lock; lock.l_type = f_rdlck; lock.l_whence = seek_set; lock.l_start = 0; lock.l_len = 0; fcntl(fd, f_setlk, &lock); gftime(buff); printf("%s: parent has read lock\n", buff); //first child if(fork() == 0){ char buf2[100] = {0}; sleep(1); gftime(buf2); printf("%s: first child tries to obtain write lock\n", buf2); struct flock lock2; lock2.l_type = f_wrlck; lock2.l_whence = seek_set; lock2.l_start = 0; lock2.l_len = 0; fcntl(fd, f_setlkw, &lock2); gftime(buf2); printf("%s: first child obtains write lock\n", buf2); sleep(2); lock2.l_type = f_unlck; lock2.l_whence = seek_set; lock2.l_start = 0; lock2.l_len = 0; fcntl(fd, f_setlk, &lock2); gftime(buf2); printf("%s: first child releases write lock\n", buf2); exit(0); } //secodn child if(fork() == 0){ char buf1[100] = {0}; sleep(3); gftime(buf1); printf("%s: second child tries to obtain read lock\n", buf1); struct flock lock1; lock1.l_type = f_rdlck; lock1.l_whence = seek_set; lock1.l_start = 0; lock1.l_len = 0; fcntl(fd, f_setlkw, &lock1); gftime(buf1); printf("%s: second child obtains read lock\n", buf1); sleep(4); lock1.l_type = f_unlck; lock1.l_whence = seek_set; lock1.l_start = 0; lock1.l_len = 0; fcntl(fd, f_setlk, &lock1); gftime(buf1); printf("%s: second child release read lock\n", buf1); exit(0); } //parent process sleep(5); lock.l_type = f_unlck; lock.l_whence = seek_set; lock.l_start = 0; lock.l_len = 0; fcntl(fd, f_setlk, &lock); gftime(buff); printf("%s: parent releases read lock\n", buff); wait(null); wait(null); exit(0); }
在ubuntu上执行结果:
17:49:44.348946: parent has read lock 17:49:45.350191: first child tries to obtain write lock 17:49:47.350155: second child tries to obtain read lock 17:49:47.350409: second child obtains read lock 17:49:49.349442: parent releases read lock 17:49:51.351197: second child release read lock 17:49:51.351582: first child obtains write lock 17:49:53.351689: first child releases write lock
第一个问题的答案:允许另一个进程的还以读的方式取得锁定
第二个问题:假如一个文件被一个进程以写的方式锁定,这时又有2个进程在等待这个锁的释放,其中一个进程是以写锁的方式等待,其中另一个进程是以读锁的方式等待,哪一个会优先取得锁?
用例子4来观察:
#include <time.h> #include <sys/time.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> void gftime(char* buf){ struct timeval tv; gettimeofday(&tv, null); long usec = tv.tv_usec; struct tm* tm = localtime(&tv.tv_sec); sprintf(buf, "%d:%d:%d.%ld",tm->tm_hour, tm->tm_min, tm->tm_sec,usec); } int main(){ char buff[100] = {0}; int fd = open("test.dat", o_rdwr | o_creat, 0664); struct flock lock; lock.l_type = f_wrlck; lock.l_whence = seek_set; lock.l_start = 0; lock.l_len = 0; fcntl(fd, f_setlk, &lock); gftime(buff); printf("%s: parent has write lock\n", buff); //first child if(fork() == 0){ char buf2[100] = {0}; sleep(1); gftime(buf2); printf("%s: first child tries to obtain write lock\n", buf2); struct flock lock2; lock2.l_type = f_wrlck; lock2.l_whence = seek_set; lock2.l_start = 0; lock2.l_len = 0; fcntl(fd, f_setlkw, &lock2); gftime(buf2); printf("%s: first child obtains write lock\n", buf2); sleep(2); lock2.l_type = f_unlck; lock2.l_whence = seek_set; lock2.l_start = 0; lock2.l_len = 0; fcntl(fd, f_setlk, &lock2); gftime(buf2); printf("%s: first child releases write lock\n", buf2); exit(0); } //secodn child if(fork() == 0){ char buf1[100] = {0}; sleep(3); gftime(buf1); printf("%s: second child tries to obtain read lock\n", buf1); struct flock lock1; lock1.l_type = f_rdlck; lock1.l_whence = seek_set; lock1.l_start = 0; lock1.l_len = 0; fcntl(fd, f_setlkw, &lock1); gftime(buf1); printf("%s: second child obtains read lock\n", buf1); sleep(4); lock1.l_type = f_unlck; lock1.l_whence = seek_set; lock1.l_start = 0; lock1.l_len = 0; fcntl(fd, f_setlk, &lock1); gftime(buf1); printf("%s: second child release read lock\n", buf1); exit(0); } //parent process sleep(5); lock.l_type = f_unlck; lock.l_whence = seek_set; lock.l_start = 0; lock.l_len = 0; fcntl(fd, f_setlk, &lock); gftime(buff); printf("%s: parent releases write lock\n", buff); wait(null); wait(null); exit(0); }
在ubuntu上执行结果:
17:49:29.796599: parent has write lock 17:49:30.797099: first child tries to obtain write lock 17:49:32.796885: second child tries to obtain read lock 17:49:34.796868: parent releases write lock 17:49:34.796987: second child obtains read lock 17:49:38.797148: second child release read lock 17:49:38.797297: first child obtains write lock 17:49:40.797727: first child releases write lock
第二个问题的答案:没有准确答案。在ubuntu上的执行结果上看,读锁优先了,但是,可能在别的环境上又是写锁优先。按道理来说应该写锁优先吧?
c/c++ 学习互助qq群:877684253
本人微信:xiaoshitou5854
上一篇: 努尔哈赤没有留遗诏,为什么皇太极继位了?
下一篇: 迭代(一):迭代算法的基本思想