線程的讀寫鎖函數: 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