zoukankan      html  css  js  c++  java
  • linux c编程:记录锁

    记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作。尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记录上锁往往更容易使用,且效率更高。
    记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区。如果我们在用读写锁对一个文件进行加锁。有2个读用户,一个写用户,在读用户读取的时候,写用户是不能操作该文件的。但是读用户也许只需要读取文件前面20个字节的内容,
    而写用户需要操作文件末尾的20个字节,这种场景下其实读用户和写用户是可以同时进行的。但是读写锁却不能满足,因此拉长了处理时间。
    因此记录锁有如下3个特点:
          记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加记录锁,来同步不同进程对某一共享资源的访问,如共享内存,I/O设备。
        • 对于劝告性上锁,当一个进程通过上锁对文件进行操作时,它不能阻止另一个非协作进程对该文件的修改。
        • 即使是强制性上锁,也不能完全保证该文件不会被另一个进程修改。因为强制性锁对unlink函数没有影响,所以一个进程可以先删除该文件,然后再将修改后的内容保存为同一文件来实现修改

    函数接口:
    //根据cmd的不同有以下三种类型的调用
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    int fcntl(int fd, int cmd, struct flock *lock);
    由函数名称可知fcntl的功能是对文件的控制操作,根据传入不同的操作类型命令cmd,fcntl会执行不同的操作,fcntl根据cmd不同,接收可变的参数。具体有以下五种类型的操作
    /*
    cmd = F_DUPFD,复制一个文件描述符;
    */
    int fcntl(int fd, int cmd);

    /*
    cmd = F_GETFD,获得文件描述符标志;
    cmd = F_SETFD,设置文件描述符标志;arg = 描述符标志的值,目前只定义了一个标志: FD_CLOEXEC
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    */

    /*
    cmd = F_GETFL,获得文件状态标志;
    cmd = F_SETFL,设置文件状态标志;arg = 状态标志的值
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    */

    /*
    cmd = F_GETOWN,获得当前接收SIGIO和SIGURG信号的进程ID或进程组ID
    cmd = F_SETOWN,设置接收SIGIO和SIGURG信号的进程ID或进程组ID;arg = 进程ID或进程组ID
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    */

    /*
    Return value:

    对于成功的调用,根据操作类型cmd不同,有以下几种情况:
           F_DUPFD  返回新的文件描述符
           F_GETFD  返回文件描述符标志
           F_GETFL  返回文件状态标志
           F_GETOWN 进程ID或进程组ID
           All other commands  返回0
    调用失败, 返回-1,并设置errno。
    */
    上面四个功能都是fcntl提供的很常用的操作,关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下:
    int fcntl(int fd, int cmd, struct flock *lock);

    /*
    cmd = F_GETLK,测试能否建立一把锁
    cmd = F_SETLK,设置锁
    cmd = F_SETLKW, 阻塞设置一把锁
    struct flock {
          short l_type;    /* 锁的类型: F_RDLCK, F_WRLCK, F_UNLCK */
          short l_whence;  /* 加锁的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */
          off_t l_start;   /* 加锁的起始偏移,相对于l_whence */
          off_t l_len;     /* 上锁的字节数*/
          pid_t l_pid;     /* 已经占用锁的PID(只对F_GETLK 命令有效) */8
          /*...*/
    };
    F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放由lock指向flock结构所描述的锁,如果无法获取锁时,该函数会立即返回一个EACCESS或EAGAIN错误,而不会阻塞。
    F_SETLKW:F_SETLKW和F_SETLK的区别是,无法设置锁的时候,调用线程会阻塞到该锁能够授权位置。
    F_GETLK:IF_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,如果没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,否则已存在的锁的信息将会写入lock所指向的flock结构中
    这里需要注意的是,用F_GETLK测试能否建立一把锁,然后接着用F_SETLK或F_SETLKW企图建立一把锁,由于这两者不是一个原子操作,所以不能保证两次fcntl之间不会有另外一个进程插入并建立一把相关的锁,从而使一开始的测试情况无效。所以一般不希望上锁时阻塞,会直接通过调用F_SETLK,并对返回结果进行测试,以判断是否成功建立所要求的锁。
    记录锁相当于读写锁的一种扩展类型,记录锁和读写锁一样也有两种锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK)。在使用规则上和读写锁也基本一样:
        • 文件给定字节区间,多个进程可以有一把共享读锁,即允许多个进程以读模式访问该字节区;
        • 文件给定字节区间,只能有一个进程有一把独占写锁,即只允许有一个进程已写模式访问该字节区;
        • 文件给定字节区间,如果有一把或多把读锁,不能在该字节区再加写锁,同样,如果有一把写锁,不能再该字节区再加任何读写锁。
    如下表所示:

    需要说明的是:上面所阐述的规则只适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求。即如果一个进程对一个文件区间已经有了一把锁,后来该进程又试图在同一文件区间再加一把锁,那么新锁将会覆盖老锁

    下面来进行测试:

    测试一:在同一进程中测试能否在加写锁后,继续加读写锁

    #include <fcntl.h>

    #include <stdio.h>


    void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)

    {

    if (lock == NULL)

    return;

    lock->l_type = type;

    lock->l_whence = whence;

    lock->l_start = start;

    lock->l_len = len;

    }


    int readw_lock(int fd)

    {

    if (fd < 0)

    {

    return -1;

    }


    struct flock lock;

    lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0);


    if (fcntl(fd, F_SETLKW, &lock) != 0)

    {

    return -1;

    }


    return 0;

    }


    int writew_lock(int fd)

    {

    struct flock lock;

    if (fd < 0)

    {

    return -1;

    }


    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);


    if (fcntl(fd, F_SETLKW, &lock) != 0)

    {

    return -1;

    }


    return 0;

    }


    int unlock(int fd)

    {

    if (fd < 0)

    {

    return -1;

    }


    struct flock lock;

    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);


    if (fcntl(fd, F_SETLKW, &lock) != 0)

    {

    return -1;

    }


    return 0;

    }


    pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)

    {

    struct flock lock;

    lock_init(&lock, type, whence, start, len);


    if (fcntl(fd, F_GETLK, &lock) == -1)

    {

    return -1;

    }


    if(lock.l_type == F_UNLCK)

    return 0;

    return lock.l_pid;

    }


    #include <stdio.h>

    #include <stdlib.h>

    #include <unistd.h>

    #include <sys/wait.h>

    #include <sys/types.h>

    #include "func1.h"


    #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)


    int main()

    {

    char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';

    pid_t t1,t2;

    int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);

    writew_lock(fd);

    t1=lock_test(fd,F_WRLCK,SEEK_SET,0,0);

    t2=lock_test(fd,F_RDLCK,SEEK_SET,0,0);

    printf("pid_t1=%ld ",t1);

    printf("pid_t2=%ld ",t2);

    unlock(fd);

    return 0;

    }

    运行结果如下:可以看到反馈的进程号都是一样的。证明同一个进程锁是可以覆盖前一个的。

    测试二:父进程中加写锁后,然后再子进程中测试能否继续加读写锁

    int main()

    {

    char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';

    pid_t t1,t2,child;

    int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);

    writew_lock(fd);

    if(child=fork() == 0)

    {

    t1=lock_test(fd,F_WRLCK,SEEK_SET,0,0);

    t2=lock_test(fd,F_RDLCK,SEEK_SET,0,0);

    printf("pid_t1=%ld ",t1);

    printf("pid_t2=%ld ",t2);

    printf("pid_child=%ld ",child);

    }

    sleep(3);

    unlock(fd);


    return 0;

    }

    测试结果如下:表明不同进程不能对已加写锁的同一文件区间,获得加锁权限;

    前面的表格可以看出在已经获取写锁的情况,只能允许读锁,而拒绝写锁。下面这个代码来测试下这种情况

    int main()

    {

    char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';

    pid_t t1,t2,child;

    int ret;

    int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);

    readw_lock(fd);

    if(fork() == 0)

    {

    printf("child1 try to get write lock ");

    ret=writew_lock(fd);

    if (ret >= 0)

    {

    printf("child1 get write lock ");

    unlock(fd);

    printf("child1 release write lock ");

    exit(0);

    }

    else

    {

    printf("child1 get write lock failed ");

    }

    exit(0);

    }

    if(fork() == 0)

    {

    sleep(3);

    printf("child2 try to get read lock ");

    readw_lock(fd);

    printf("child2 get read lock ");

    unlock(fd);

    printf("child2 release read lock ");

    exit(0);

    }

    sleep(10);

    unlock(fd);

    return 0;

    }

    执行结果:

    可以看出在父进程获取读锁的情况下,child1进程对文件同一区域获取写锁失败,而child2进程可以获取到读锁

    下面的代码来测试父进程获得写锁,child1试图获取写锁,child2试图获取读锁

    int main()

    {

    char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';

    pid_t t1,t2,child;

    int ret1,ret2;

    int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);

    writew_lock(fd);

    if(fork() == 0)

    {

    printf("child1 try to get write lock ");

    ret1=writew_lock(fd);

    if (ret1 >= 0)

    {

    printf("child1 get write lock ");

    unlock(fd);

    printf("child1 release write lock ");

    exit(0);

    }

    else

    {

    printf("child1 get write lock failed ");

    }

    exit(0);

    }

    if(fork() == 0)

    {

    sleep(3);

    printf("child2 try to get read lock ");

    ret2=readw_lock(fd);

    if(ret2 >= 0)

    {

    printf("child2 get read lock ");

    unlock(fd);

    printf("child2 release read lock ");

    }

    else

    {

    printf("child2 get read lock failed ");

    }

    exit(0);

    }

    sleep(10);

    unlock(fd);

    return 0;

    }

    执行结果:

    child1进程获取写锁失败,child2进程获取读锁失败

  • 相关阅读:
    eclipse本地覆盖版本库
    数据库表分区
    oracle监听器启动,实例启动
    Mac 终端命令大全
    Mac基本用法
    wen前端学习计划
    css3animation模仿百度音乐的唱片旋转效果
    获取HTML DOM节点元素的方法的总结
    简单CSS技巧实现的Logo动画效果(很像导航栏上经常运用的效果,但有差别)
    详解 CSS 属性
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/10631487.html
Copyright © 2011-2022 走看看