进程同步之POSIX信号量与文件锁的使用代码实例

以代码形式对信号量与文件锁进行说明。

一 信号量

1、信号量的创建与读取

#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

void main()
{
    sem_t *sem;

    /*可以随便一个名字,它会被创建成小型 POSIX 共享内存对象,
	其名字的形式为 sem.name,这些对象将被放在一个挂载在/dev/shm 目录之下的专用 tmpfs 文
	件系统中。这个文件系统具备内核持久性——它所包含的信号量对象将会持久存在,
	即使当前没有进程打开它们,但如果系统被关闭的话,这些对象就会丢失。*/
    char *sem_name = "sem_test";	

    // O_CREAT,表示新建,如果已存在,则会创建失败返回NULL。O_EXCL,用来读取。
    // O_RDONLY、 O_WRONLY 以及 O_RDWR,一般使用读写模式,因为都需要进行加1减1操作。
    sem = sem_open(sem_name, O_CREAT | O_EXCL, O_RDWR, 2);

    if (sem == SEM_FAILED) {
        printf("sem open failed\n");
    } else {
        int value = -1;
        printf("sem open succ\n");
        // sem_getvalue()返回时, sval 中的返回值可能已经过时了
        if (sem_getvalue(sem, &value) != -1)
            printf("sem get value succ, value:%d\n", value);
        else
            printf("sem get value failed, value:%d\n", value);
    }
    
    // 打开的命名信号量在进程终止或进程执行了一个 exec()时会自动被关闭。
	// 关闭一个信号量并不会删除这个信号量,而要删除信号量则需要使用 sem_unlink()。
    sem_close(sem);

    // 如果没有删除,那它将会持久存在,包括程序意外崩溃后,它仍会存在
    sem_unlink(sem_name);
}

编译需要链接pthread库:

gcc sem_create.c -lpthread

2、创建新进程读取信号量

#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

void main()
{
    sem_t *sem;
    char *sem_name = "sem_test";

    // 只读取,不需要后面的两个参数
    sem = sem_open(sem_name, O_EXCL);

    if (sem == SEM_FAILED) {
        printf("sem open failed\n");
    } else {
        int value = -1;
        printf("sem open succ\n");
        if (sem_getvalue(sem, &value) != -1)
            printf("sem get value succ, value:%d\n", value);
        else
            printf("sem get value failed, value:%d\n", value);
    }
}

二 文件锁

给文件加锁的 API有两种,分为flock和fcntl。
flock()对整个文件加锁。
fcntl()对一个文件区域加锁。
flock()系统调用源自 BSD,而 fcntl()则源自 System V。

1、fcntl 文件锁的创建

#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

/******************************************************************************
 * @description: 给文件区域上锁
 * @param [] fd: 文件描述符
 * @param [] cmd:    F_GETLK,检测是否能够获取flock结构体指定的区域上的锁,但实际不获取这把锁。l_type字段的值必须为F_RDLCK或F_WRLCK。
                     F_SETLK,l_type是F_RDLCK或F_WRLCK时,表示获取锁; l_type是F_UNLCK时,表示释放锁.
                     F_SETLKW,阻塞等待冲突的锁被释放,设置的锁因为其他锁而被阻止设置时,该命令会等待相冲突的锁被释放。
 * @param [] type:   F_RDLCK-共享读锁, F_WRLCK-独占写锁, F_UNLCK-删除锁.读写与文件打开模式相关联
 * @param [] whence: SEEK_SET-起始位置, SEEK_CUR, SEEK_END
 * @param [] start:  锁住的区域起点
 * @param [] len:    锁住的区域长度,0表示锁住整个文件
 * @return -1,失败; 0,成功
 *****************************************************************************/
static int lock_reg(int fd, int cmd, int type, int whence, int start, off_t len)
{
    int status = -1;
    struct flock fl;

    if (fd < 0) return -1;

    fl.l_type   = type;
    fl.l_whence = whence;
    fl.l_start  = start;
    fl.l_len    = len;

    status = fcntl(fd, cmd, &fl);
    if (status == 0) {
        if (fl.l_type == F_RDLCK) printf("[PID=%ld] read locked succ\n", (long)getpid());
        else if (fl.l_type == F_WRLCK) printf("[PID=%ld] write locked succ\n", (long)getpid());
        else if (fl.l_type == F_UNLCK) printf("[PID=%ld] unlocked succ\n", (long)getpid());
    } else if (errno == EAGAIN || errno == EACCES) {
        status = 1;
        printf("[PID=%ld] fcntl failed, incompatible lock, status:%d\n", (long)getpid(), status);
    } else if (errno == EDEADLK) {
        status = 2;
        printf("[PID=%ld] fcntl failed, dead lock, status:%d\n", (long)getpid(), status);
    } else {
        status = -1;
        printf("failed\n");
    }

    return status;
}

// 非阻塞上锁
static int lock_region(int fd, int type, int whence, int start, off_t len)
{
    return lock_reg(fd, F_SETLK, type, whence, start, len);
}

// 阻塞式上锁
static int lock_region_wait(int fd, int type, int whence, int start, off_t len)
{
    return lock_reg(fd, F_SETLKW, type, whence, start, len);
}

// 检测文件区域是否可以被上锁,如果可以返回0;如果已被上锁,则返回上锁的进程PID;否则返回其他错误
static pid_t region_is_locked(int fd, int type, int whence, int start, int len)
{
    int status = -1;
    struct flock fl;

    if (fd < 0) return -1;

    fl.l_type   = type;   // F_RDLCK-共享读锁, F_WRLCK-独占写锁, F_UNLCK-删除锁.读写与文件打开模式相关联
    fl.l_whence = whence; // SEEK_SET-起始位置, SEEK_CUR, SEEK_END
    fl.l_start  = start;  // 锁住的区域起点
    fl.l_len    = len;    // 锁住的区域长度,0表示锁住整个文件

    status = fcntl(fd, F_GETLK, &fl);
    if (status == -1) {
        printf("[PID=%ld] fcntl failed, %s\n", (long)getpid(), strerror(errno));
    } else {
        if (fl.l_type == F_UNLCK) printf("[PID=%ld] available, lock can be placed\n", (long)getpid());
        else {
            printf("[PID=%ld] denied by %s, held by pid %ld\n", \
                    (long)getpid(), \
                    (fl.l_type == F_RDLCK)?"READ":"WRITE", \
                    (long)fl.l_pid);
        }
    }

    return (fl.l_type == F_UNLCK)? 0 : fl.l_pid;
}

void main()
{
    int fd, status;
    char *file_name = "tfile";

    // 不存在加锁文件,就先创建
    if (access(file_name, R_OK) != 0) {
        fd = open(file_name, O_RDWR | O_CREAT);
        if (fd > 0) close(fd);
    }

    fd = open(file_name, O_RDWR);
    if (fd == -1) {
        printf("open file failed\n");
        return;
    }

    // 由于 stdio 库会在用户空间进行缓冲,因此在混合使用stdio函数与加锁技术时需要特别小心
    // 这里的问题是一个输入缓冲器在被加锁之前可能会被填满或者一个输出缓冲器在锁被删除之后可能会被刷新
    // 为此,有3种解决方法:
    // (1) 使用 read()和 write()(以及相关的系统调用)取代 stdio 库来执行文件 I/O。
    // (2) 在对文件加锁之后立即通过fflush刷新stdio流,并且在释放锁之前立即再次刷新这个流。
    // (3) 使用 setbuf()(或类似的函数)来禁用 stdio 缓冲,当然这可能会牺牲一些效率。
    fflush(stdout);  // <unix系统编程手册>例子是放在这边调用,有点奇怪

    status = lock_region(fd, F_WRLCK, SEEK_SET, 0, 0);
    if (status != 0) {
        printf("lock failed\n");
    }

    // (1) 由fork()创建的子进程不会继承记录锁。这与flock()是不同的,在使用flock()创建的锁时,
    // 子进程会继承一个引用同一把锁的引用并且能够释放这把锁,从而导致父进程也会失去这把锁。
    // (2) 记录锁在 exec()中会得到保留,除非设置标识close-on-exec)
    // (3) 一个进程中的所有线程会共享同一组记录锁
    // (4) 记录锁同时与一个进程和一个i-node关联。从这种关联关系可以得出,当一个进程终止之后,其所有记录锁会被释放。
    // 另一个稍微有点出乎意料的结果是当一个进程关闭了一个文件描述符之后,进程持有的对应文件上的所有锁会被释放,不管这些锁是通过哪个文件描述符获得的。
    if (fork()==0) {
        printf("child process\n");
        for(;;){}
    } else {
        printf("parents process\n");
    }

    // 这边如果注释掉,尽管没有显式关闭fd,但是主进程结束了,锁也释放了,可以被其他进程上锁
    // if (fd > 0) close(fd);
}

2、fcntl 文件锁的获取

#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

/******************************************************************************
 * @description: 给文件区域上锁
 * @param [] fd: 文件描述符
 * @param [] cmd:    F_GETLK,检测是否能够获取flock结构体指定的区域上的锁,但实际不获取这把锁。l_type字段的值必须为F_RDLCK或F_WRLCK。
                     F_SETLK,l_type是F_RDLCK或F_WRLCK时,表示获取锁; l_type是F_UNLCK时,表示释放锁.
                     F_SETLKW,阻塞等待冲突的锁被释放,设置的锁因为其他锁而被阻止设置时,该命令会等待相冲突的锁被释放。
 * @param [] type:   F_RDLCK-共享读锁, F_WRLCK-独占写锁, F_UNLCK-删除锁.读写与文件打开模式相关联
 * @param [] whence: SEEK_SET-起始位置, SEEK_CUR, SEEK_END
 * @param [] start:  锁住的区域起点
 * @param [] len:    锁住的区域长度,0表示锁住整个文件
 * @return -1,失败; 0,成功
 *****************************************************************************/
static int lock_reg(int fd, int cmd, int type, int whence, int start, off_t len)
{
    int status = -1;
    struct flock fl;

    if (fd < 0) return -1;

    fl.l_type   = type;
    fl.l_whence = whence;
    fl.l_start  = start;
    fl.l_len    = len;

    status = fcntl(fd, cmd, &fl);
    if (status == 0) {
        if (fl.l_type == F_RDLCK) printf("[PID=%ld] read locked succ\n", (long)getpid());
        else if (fl.l_type == F_WRLCK) printf("[PID=%ld] write locked succ\n", (long)getpid());
        else if (fl.l_type == F_UNLCK) printf("[PID=%ld] unlocked succ\n", (long)getpid());
    } else if (errno == EAGAIN || errno == EACCES) {
        status = 1;
        printf("[PID=%ld] fcntl failed, incompatible lock, status:%d\n", (long)getpid(), status);
    } else if (errno == EDEADLK) {
        status = 2;
        printf("[PID=%ld] fcntl failed, dead lock, status:%d\n", (long)getpid(), status);
    } else {
        status = -1;
        printf("failed\n");
    }

    return status;
}

// 非阻塞上锁
static int lock_region(int fd, int type, int whence, int start, off_t len)
{
    return lock_reg(fd, F_SETLK, type, whence, start, len);
}

// 阻塞式上锁
static int lock_region_wait(int fd, int type, int whence, int start, off_t len)
{
    return lock_reg(fd, F_SETLKW, type, whence, start, len);
}

// 检测文件区域是否可以被上锁,如果可以返回0;如果已被上锁,则返回上锁的进程PID;否则返回其他错误
static pid_t region_is_locked(int fd, int type, int whence, int start, int len)
{
    int status = -1;
    struct flock fl;

    if (fd < 0) return -1;

    fl.l_type   = type;   // F_RDLCK-共享读锁, F_WRLCK-独占写锁, F_UNLCK-删除锁.读写与文件打开模式相关联
    fl.l_whence = whence; // SEEK_SET-起始位置, SEEK_CUR, SEEK_END
    fl.l_start  = start;  // 锁住的区域起点
    fl.l_len    = len;    // 锁住的区域长度,0表示锁住整个文件

    // F_GETLK时,fcntl会重新给fl赋值
    status = fcntl(fd, F_GETLK, &fl);
    if (status == -1) {
        printf("[PID=%ld] fcntl failed, %s\n", (long)getpid(), strerror(errno));
    } else {
        if (fl.l_type == F_UNLCK) printf("[PID=%ld] available, lock can be placed\n", (long)getpid());
        else {
            printf("[PID=%ld] denied by %s, held by pid %ld\n", \
                    (long)getpid(), \
                    (fl.l_type == F_RDLCK)?"READ":"WRITE", \
                    (long)fl.l_pid);
        }
    }

    return (fl.l_type == F_UNLCK)? 0 : fl.l_pid;
}

void main()
{
    int fd, status;
    char *file_name = "tfile";

    fd = open(file_name, O_RDONLY);
    if (fd == -1) {
        printf("open file failed\n");
        return;
    }

    // 由于 stdio 库会在用户空间进行缓冲,因此在混合使用stdio函数与加锁技术时需要特别小心
    // 这里的问题是一个输入缓冲器在被加锁之前可能会被填满或者一个输出缓冲器在锁被删除之后可能会被刷新
    // 为此,有3种解决方法:
    // (1) 使用 read()和 write()(以及相关的系统调用)取代 stdio 库来执行文件 I/O。
    // (2) 在对文件加锁之后立即通过fflush刷新stdio流,并且在释放锁之前立即再次刷新这个流。
    // (3) 使用 setbuf()(或类似的函数)来禁用 stdio 缓冲,当然这可能会牺牲一些效率。
    fflush(stdout);  // <unix系统编程手册>例子是放在这边调用,有点奇怪

    region_is_locked(fd, F_RDLCK, SEEK_SET, 0, 0);
    
    if (fd > 0) close(fd);
}

3、flock 文件锁的创建

#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/file.h>

void main()
{
    int fd, lock, status;
    const char *file_name = "tfile";

    // 不存在加锁文件,就先创建
    if (access(file_name, R_OK) != 0) {
        fd = open(file_name, O_RDWR | O_CREAT);
        if (fd > 0) close(fd);
    }

    // 不管一个进程在文件上的访问模式是什么(读、写、或读写),它都可以在文件上放置一把共享锁或互斥锁
    fd = open(file_name, O_RDWR);
    if (fd == -1) {
        printf("open file failed\n");
        return;
    }

    // LOCK_SH,共享锁; LOCK_EX,互斥锁;LOCK_UN,解锁; LOCK_NB,非阻塞锁请求,用或|符号来添加到参数中
    lock = LOCK_EX | LOCK_NB;
    status = flock(fd, lock);
    if (status == -1) {
        if (errno == EWOULDBLOCK) printf("already locked\n");
        else printf("flock failed, pid=%ld\n", (long)getpid());
    } else {
        printf("lock succ\n");
    }

    // 当使用 fork()创建一个子进程时,这个子进程会复制其父进程的文件描述符,并且与使用dup()调用之类的函数复制的描述符一样,
    // 这些描述符会引用同一个打开的文件描述,进而会引用同一个锁。例如下面的代码会导致一个子进程删除一个父进程的锁
    if (fork() == 0) {
        printf("child lock\n");
        //flock(fd, LOCK_UN);
        sleep(30);
        printf("exit\n");
        exit(1);
    }

    // 通过再次调用 flock()并在 operation 参数中指定恰当的值可以将一个既有共享锁转换成一个互斥锁(反之亦然)
    // 将一个共享锁转换成一个互斥锁,在另一个进程持有了文件上的共享锁时会阻塞,除非同时指定了 LOCK_NB 标记。
    // lock = LOCK_UN;
    // status = flock(fd, lock);
    // if (status == -1) printf("unlock failed\n");
    // else printf("unlock succ\n");

    close(fd);
}

4、flock 文件锁的获取

#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/file.h>

void main()
{
    int fd, lock, status;
    const char *file_name = "tfile";

    // 不管一个进程在文件上的访问模式是什么(读、写、或读写),它都可以在文件上放置一把共享锁或互斥锁
    fd = open(file_name, O_RDONLY);
    if (fd == -1) {
        printf("open file failed\n");
        return;
    }

    // LOCK_SH,共享锁; LOCK_EX,互斥锁;LOCK_UN,解锁; LOCK_NB,非阻塞锁请求,用或|符号来添加到参数中
    lock = LOCK_EX | LOCK_NB;
    status = flock(fd, lock);
    if (status == -1) {
        if (errno == EWOULDBLOCK) printf("already locked\n");
        else printf("flock failed, pid=%ld\n", (long)getpid());
    } else {
        printf("lock succ\n");
    }

    close(fd);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值