zoukankan      html  css  js  c++  java
  • Linux 文件锁与记录锁

    基本概念

    记录锁

    记录上锁(record locking)是读写锁的一种扩展类型,可用于亲缘进程或无亲缘进程之间共享某个文件的读和写,常简称为记录锁。读写锁可参见这篇文章:Linux 自旋锁,互斥量(互斥锁),读写锁

    记录锁锁定的文件通过文件描述符访问,调用fcntl执行上锁和解锁操作。记录锁的维护通常在内核中,属主是由属主进程ID标识。也就是说,记录锁用于不同进程间的上锁,而不是用于同一进程内不同线程间的上锁。

    文件锁

    记录锁可以指定文件中待上锁或解锁部分的字节范围(byte range)。当记录锁锁定的范围是整个文件时,就称为文件上锁(file locking,简称文件锁)。可以说,文件锁是一种特殊的记录锁。

    粒度

    粒度(granularity)用于标记能被锁住的对象的大小。对于Posix记录锁,粒度是单个字节。对于文件锁,粒度是整个文件。

    记录锁与读写锁

    记录锁和读写锁的上锁方式都分为:读出锁、写入锁。读出锁可以同时有多个,写入锁同时只能有一个。

    不过,读写锁是pthread_rwlock_t类型的变量在内存中分配的。当读写锁是在单个进程内的各个线程间共享时(默认属性),分配的变量可以在单个进程内;当读写锁在共享内存区时,各进程共享读写锁变量。
    也就是说,读写锁锁定的对象是内存中的锁变量,可用于多进程,也可以用于多线程。

    记录锁锁定的对象是记录(文件的某些字节范围),用于多进程。

    Posix记录上锁

    函数接口fcntl

    #include <fcntl.h>
    
    int fcntl(int fd, int cmd, ... /* arg: struct flock* */);
    

    参数

    • fd 已打开文件的文件描述符
    • cmd 有3个取值,代表3个命令:F_SETLK, F_SETLKW, F_GETLK。
    • arg flock结构对象

    flock定义

    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
                           (F_GETLK only) */
       ...
    };
    

    cmd3个命令:

    取值 描述
    F_SETLK 获取(l_type = F_RDLCK或F_WRLCK)或释放(l_type=F_UNLCK)由arg指向的flock结构所描述的锁。
    如果无法将该锁授予调用进程,该函数就立即返回一个EACCESS或EAGAIN错误而不阻塞。
    F_SETLKW 与F_SETLK类似,区别在于调用线程如果无法取得锁,调用线程将阻塞到该锁能取得为止。名字最后的W是“wait”的意思。
    F_GETLK 检查是否有线程/进程已获得锁。如果当前没有上锁,由arg指向的flock.l_type会被置为F_UNLCK;否则,由arg指向的flock对象就会包含持有该锁的进程ID。

    返回值
    成功取决于cmd;出错,返回-1。

    既然返回值已经能表示获得锁的结果,为何还需要cmd=F_GETLK命令?
    因为当执行fcntl + F_SETLK/F_SETLKW命令返回一个错误时,导致该错误的某个锁的信息可以由F_GETLK命令返回,从而允许确定是哪个进程锁住了所请求的文件区,及上锁方式(读出锁或写入锁)。当然,当文件区解锁时,F_GETLK命令也可能返回文件区解锁的信息。

    flock结构锁住文件区

    上锁类型
    flock对文件区的上锁类型由l_type成员决定:

    • F_RDLCK 读出方式上锁
    • F_WRLCK 写入方式上锁
    • F_UNLCK 解锁(不论之前以读出,还是写入方式上锁)

    锁定范围
    flock对文件区的锁定范围取决于3个成员值l_whence + l_start + l_len

    l_whence用来解释l_start起始位置,粗略定位起始位置;l_start(相对偏移)是在l_whence基础上的偏移;l_len指定从l_whence和l_start决定的偏移开始的连续字节数。

    下面根据l_whence 取值来解释文件区锁定范围:

    • SEEK_SET: l_start相对于文件的开头解释,锁定范围为[l_start, l_len);
    • SEEK_CUR: l_start相对于文件的当前字节偏移(即文件当前读写指针位置)解释,锁定范围为[cur_pos + l_start, cur_pos + l_len)(假设当前文件读写指针位于cur_pos(相对于文件起始位置而言的偏移));
    • SEEK_END: l_start相对于文件的末尾解释,锁定范围为[file_size+ l_start, file_size+ l_len)(假设当前文件字节数为file_size);

    锁住整个文件
    锁住整个文件的方式有2种:
    1)设置flock结构成员l_whence = SEEK_SET,l_start = 0, l_len = 0(0表示不限制字节数);
    2)调用lseek将读写指针定位到文件开头,然后设置flock结构成员l_whence = SEEK_CUR,,l_start = 0, l_len = 0(0表示不限制字节数);

    最后,调用fcntl F_SETLK/F_SETLKW获得锁;

    常用第1)种方式。

    记录锁的释放
    1)通过设置flock.l_type = F_UNLCK,调用fcntl F_SETLK/F_SETLKW来解锁;
    2)通过关闭与文件关联的进程,会关闭所有文件描述符,删除锁;

    问题:记录锁能应用于多线程吗?

    先不着急下结论,而是做个实验:创建2个线程tid1和tid2,观察tid1获得记录锁期间,tid2是否也能同时获得锁。
    如果tid1获得记录锁的时候,tid2也能获得记录锁,说明记录锁不能用于多线程;否则,说明记录锁可以用于多线程。

    void *thread_test_flock1(void *arg)
    {
        int id = (int)arg;
    
        printf("hello, thread %d
    ", id);
        writew_lock(file_fd);
        printf("thread %d get write flock
    ", id);
        sleep(5);
    
        printf("thread %d work
    ", id);
        un_lock(file_fd);
        printf("thread %d release write flock
    ", id);
    }
    
    void *thread_test_flock2(void *arg)
    {
        int id = (int)arg;
    
        printf("hello, thread %d
    ", id);
        sleep(2);
        printf("thread %d get write flock
    ", id);
        writew_lock(file_fd);
    
        printf("thread %d work
    ", id);
        un_lock(file_fd);
        printf("thread %d release write flock
    ", id);
    }
    

    这是获得写入锁和解锁的操作:

    int writew_lock(int fd)
    {
        struct flock lock;
        lock.l_type = F_WRLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;
        lock.l_pid = getpid();
    
        if (fcntl(fd, F_SETLKW, &lock) < 0) {
            perror("fcntl error");
            exit(1);
        }
        return 0;
    }
    
    int un_lock(int fd)
    {
        struct flock lock;
        lock.l_type = F_UNLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;
        lock.l_pid = getpid();
    
        if (fcntl(fd, F_SETLKW, &lock) < 0) {
            perror("fcntl error");
            exit(1);
        }
        return 0;
    }
    

    file_fd定义及创建线程的main:

    int file_fd = -1;
    int main()
    {
        pthread_t tid1, tid2;
    
        if ((file_fd = open("test_flock.txt", O_CREAT | O_WRONLY)) < 0) {
            perror("open error");
            exit(1);
        }
    
        pthread_create(&tid1, NULL, thread_test_flock1, (void *)1);
        pthread_create(&tid2, NULL, thread_test_flock2, (void *)2);
    
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
    
        return 0;
    }
    

    运行结果:
    表明在线程tid1获得写入锁时,tid2页获得了写入锁。说明记录锁不能应用于多线程环境。

    hello, thread 1
    thread 1 get write flock
    hello, thread 2
    thread 2 get write flock
    thread 2 work
    thread 2 release write flock
    thread 1 work
    thread 1 release write flock
    

    问题:记录锁能用于多进程吗?

    虽然我们已经知道是可以,不过,这里还是实验验证下,跟前面多线程环境做对比。

    获得写入锁、解锁操作,跟前面一样,保持不变。main函数见下:

    int main()
    {
        pid_t pid1, pid2;
        int fd = open("test_flock.txt", O_CREAT | O_WRONLY, 0774);
        if (fd < 0) {
            perror("open error");
            exit(1);
        }
    
        if ((pid1 = fork()) < 0) { // error
            perror("fork1 error");
            exit(1);
        }
        else if (pid1 == 0) { // child - pid1
            if ((pid2 = fork()) < 0) {// error
                perror("fork2 error");
                exit(1);
            }
            else if (pid2 == 0){// child - pid2
                sleep(1);
                printf("hello, pid2
    ");
    
                writew_lock(fd);
                printf("pid2 get write lock
    ");
    
                un_lock(fd);
                printf("pid2 unlock
    ");
            }
            // pid1
            printf("hello, pid1
    ");
    
            writew_lock(fd);
            printf("pid1 get write lock
    ");
            sleep(5);
    
            un_lock(fd);
            printf("pid1 unlock
    ");
        }
    
        int status;
        while (waitpid(-1, &status, 0) > 0) {
            if (errno == ECHILD) {
                break;
            }
        }
        return 0;
    }
    

    运行结果:
    可以看出,在进程pid1释放记录锁前,pid2无法获得锁。

    hello, pid1
    pid1 get write lock
    hello, pid2
    pid1 unlock
    pid2 get write lock
    pid2 unlock
    hello, pid1
    pid1 get write lock
    pid1 unlock
    

    文件上锁

    文件上锁通常使用flock()函数进行,相比较记录上锁的调用fcntl,传入flock结构更简便。

    flock函数接口

    #include <sys/file.h>
    
    int flock(int fd, int operation);
    

    参数

    • fd 已打开文件描述符。
    • operation 上锁操作可以是下面几个取值:
    operation值 描述
    LOCK_SH Place a shared lock. More than one process may hold a shared lock for a given file at a given time.
    LOCK_EX Place an exclusive lock. Only one process may hold an exclusive lock for a given file at a given time.
    LOCK_UN Remove an existing lock held by this process.

    可以看出,LOCK_SH用于共享锁,对应记录上锁的读锁;LOCK_EX用于独占锁,对应记录上锁的写入锁;LOCK_UN用于解锁。

    返回值
    成功,返回0;出错,返回-1.

    问题:文件上锁flock能应用于多线程环境?

    答案是不行的。验证方式类似前面的,将获得写入锁writew_lock,和解锁un_lock,都换成flock函数。

    #include <sys/file.h>
    
    void *thread_test_flock1(void *arg)
    {
        int id = (int)arg;
    
        printf("hello, thread %d
    ", id);
    //    writew_lock(file_fd);
        flock(file_fd, LOCK_EX); // 取得独占锁
        printf("thread %d get write flock
    ", id);
        sleep(5);
    
        printf("thread %d work
    ", id);
    //    un_lock(file_fd);
        flock(file_fd, LOCK_UN); // 释放锁
        printf("thread %d release write flock
    ", id);
    }
    
    void *thread_test_flock2(void *arg)
    {
        int id = (int)arg;
    
        printf("hello, thread %d
    ", id);
        sleep(2);
        printf("thread %d get write flock
    ", id);
    //    writew_lock(file_fd);
        flock(file_fd, LOCK_EX); // 取得独占锁
    
        printf("thread %d work
    ", id);
    //    un_lock(file_fd);
        flock(file_fd, LOCK_UN); // 释放锁
    
        printf("thread %d release write flock
    ", id);
    }
    

    运行结果:
    可以看到,在线程1释放独占锁之前,线程2也获得了独占锁。

    hello, thread 1
    thread 1 get write flock
    hello, thread 2
    thread 2 get write flock
    thread 2 work
    thread 2 release write flock
    thread 1 work
    thread 1 release write flock
    

    参考

    UNP 卷2

  • 相关阅读:
    Atitit.播放系统规划新版本 v4 q18 and 最近版本回顾
    Atitit.播放系统规划新版本 v4 q18 and 最近版本回顾
    atitit.极光消息推送服务器端开发实现推送  jpush v3. 总结o7p
    atitit.极光消息推送服务器端开发实现推送  jpush v3. 总结o7p
    Atitit.文件搜索工具 attilax 总结
    Atitit.文件搜索工具 attilax 总结
    Atitit.软件命名空间  包的命名统计 及命名表(2000个名称) 方案java package
    Atitit.软件命名空间  包的命名统计 及命名表(2000个名称) 方案java package
    Atitit..状态机与词法分析  通用分词器 分词引擎的设计与实现 attilax总结
    Atitit..状态机与词法分析  通用分词器 分词引擎的设计与实现 attilax总结
  • 原文地址:https://www.cnblogs.com/fortunely/p/15219611.html
Copyright © 2011-2022 走看看