欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

Linux 读写锁

本人微信:xiaoshitou5854