[C++] 使用flock构建文件锁,进程读写锁

C语言文件锁 flock

应用场景:

在多进程环境中,多个进程可能会同时访问共享资源,这时就需要考虑如何确保数据的一致性,避免出现竞态条件和数据损坏的情况。例如,在一个业务中多个进程自检需要用到共享内存。而在使用共享内存时,需要考虑多进程同时写入时不发生冲突的问题,基于使用的系统是Linux发行版,解决方案有以下两个:

  1. 使用信号量实现互斥。
  2. 使用C语言的文件锁 flock 函数,利用其独享的性质,来实现进程间的互斥访问。而且,相比较于信号量,使用flock函数构成的互斥锁,在进程以外退出时,进程所持有的文件锁会自动解锁。

flock

PS: flock函数是进程安全的,但不是线程安全的,故使用时可能需要自行加锁。

man手册:

man 2 flock

头文件及声明

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

参数

参数这建议自己查看man手册的内容。

  • fd: 一个已打开的文件描述符。
  • operation: 文件锁选项, 包括 LOCK_SH(共享锁), LOCK_EX(独占锁), LOCK_UN(解锁),在文件加锁时,若文件已加锁,则会处于程序会处于阻塞,如果希望程序不阻塞,则operation选项需或上LOCK_NB,例如,operation传入 LOCK_EX|LOCK_NB。

返回值:

失败时返回-1

简单的文件锁示例代码

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>

#define LOCKFILE "/tmp/lockfile/.lockfile"

int main()
{
    int fd = 0;
    int ret = 0;

    do {
        fd = open(LOCKFILE, O_RDONLY);
        if (-1 == fd)
        {
            fd = open(LOCKFILE, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
            if (-1 == fd)
            {
                printf ("open() for [%s] failed.\n", LOCKFILE);
                break;
            }
        }

        // file lock
        ret = flock(fd, LOCK_EX);
        if (-1 == ret)
        {    
            printf ("flock() failed.\n");
            break;
        }

        // do something
        // ...

        flock(fd, LOCK_UN);
    }
    while (0);

    if (fd != -1)
    {
        close(fd);
	}
    return 0;
}

进程读写锁代码

代码如下:

测试环境:C++20, 由于使用了共享锁,所以建议c++标准起码>=C++17

Result.hpp

#ifndef RESULT_H
#define RESULT_H 1

// 这个枚举是我习惯用于作为返回值, 此处模拟的是rust的Result
// 加上class是为了用c++的强类型系统
enum class Result 
{
    OK,
    ERROR,
    PARA_ERROR,
    RANGE_ILLEGAL,
    FILE_LOCK_FAIL,
    MKDIR_FAIL
};

#endif

ProcLock.hpp

#ifndef _PROC_LOCK_HPP_
#define _PROC_LOCK_HPP_ 1

#include "Result.hpp"

// 这里为进程锁的接口头文件
namespace ProcLock
{
	// 进程锁的类型,即用于锁哪个共享资源,可以自己添加
	// 例如我想锁一个共享空间是用于存放证书的,那么可以添加 CERT_TYPE
	enum class Type 
	{
		TEST_TYPE,
		CERT_TYPE,
		MAX_NUMS// 不使用, 仅用于计数
	};

	// 进程锁接口函数,包括独占锁和读写锁
	Result Lock(const Type type);
	Result UnLock(const Type type);
	Result SharedLock(const Type type);
    Result SharedUnLock(const Type type);
	
	// 用于进程锁的RAII类,实现构造函数加锁,析构函数解锁,防止遗忘
	class RAII_Lock
	{
	private:
		ProcLock::Type m_type;
	public:
		explicit RAII_Lock(const ProcLock::Type type)
			: m_type(type)
		{
			Lock(m_type);
		}
		~RAII_Lock()
		{
			UnLock(m_type);
		}
	};

	class RAII_ShardLock
	{
	private:
		ProcLock::Type m_type;
	public:
		explicit RAII_ShardLock(const ProcLock::Type type)
			: m_type(type)
		{
			SharedLock(m_type);
		}
		~RAII_ShardLock()
		{
			SharedUnLock(m_type);
		}
	};
}
#endif

ProcLock.cpp

#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <cstdio>
#include <cstring>
#include <cstdlib>

#include <mutex>
#include <filesystem>
#include <shared_mutex>

#include "ProcLock.hpp"

// 文件锁的文件模式,用于创建一个新文件
#define LOCK_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
// 文件锁文件的存放路径
constexpr char PROC_LOCK_FILE_PATH[] = "/tmp/.lock/";
// 总共有多少个文件锁类型
constexpr unsigned PROC_LOCK_MAX_NUM = static_cast<unsigned>(ProcLock::Type::MAX_NUMS);

// 进程锁类,使用类是为了其数据成员为私有,其实使用static定义全局变量也行
class CProcLock
{
private:
	// 进程锁初始化标志
	bool m_is_init;
	// flock对应的fd数组
	int m_fds[PROC_LOCK_MAX_NUM];
	// 线程读写锁,为了实现进程锁线程安全
    std::shared_mutex m_rw_mtxs[PROC_LOCK_MAX_NUM];
    // 互斥锁,为了锁住进程读锁的数量
    std::mutex m_shard_nums_mtxs[PROC_LOCK_MAX_NUM];
    // 统计当前有多少个线程持有进程读锁,进程读锁计数器
	unsigned m_shared_lock_nums[PROC_LOCK_MAX_NUM];
public:
	CProcLock() noexcept
		: m_is_init(false) // 把进程锁初始状态设定为false 
	{
		// 使用c++17引入的文件系统
		
		// ec是为了避免抛出异常,这个在boost库中很常见,相当于C语言的errno
		std::error_code ec; 
		
		char file_name[256] = {0};
		// c++17文件系统引入的类
		// 参考链接: https://zh.cppreference.com/w/cpp/filesystem/directory_entry
		std::filesystem::directory_entry dir(PROC_LOCK_FILE_PATH); 
		do 
		{
			// 判断存放进程锁文件的目录是否存在以及是否为目录
			if (dir.exists(ec) != true || dir.is_directory(ec) != true)
			{
				// 若该路径代表的不是目录,则删除该文件
				if (dir.is_directory(ec) != true)
				{
					// 参考链接: https://zh.cppreference.com/w/cpp/filesystem/remove
					std::filesystem::remove(PROC_LOCK_FILE_PATH, ec);
				}
				// 创造该目录
				// 参考链接: https://zh.cppreference.com/w/cpp/filesystem/create_directory
				if (std::filesystem::create_directory(PROC_LOCK_FILE_PATH, ec) != true)
				{
					std::printf("in CProcLock:: create_directory() failed.\n");
					break;
				}
			}
			for (unsigned i = 0U; i < PROC_LOCK_MAX_NUM; i++)
			{
				::memset(file_name, 0, sizeof(file_name));
				::snprintf(file_name, sizeof(file_name), "%s%04u.lockfile", PROC_LOCK_FILE_PATH, i);
				// 初始化flock所需要的文件描述符数组
				m_fds[i] = ::open(file_name, O_RDWR|O_CREAT, LOCK_MODE);
                // 将读锁计数器的数目初始化为0
                m_shared_lock_nums[i] = 0U; 
                // 文件描述符打开失败
				if (-1 == m_fds[i]) // open fail, break
				{
					break;
				}
				// 文件描述符数组初始化完毕,将进程锁初始化状态设为true
				if (PROC_LOCK_MAX_NUM-1 == i) // end, set init flag true
				{
					m_is_init = true; 
				}
			}
		}
		while (0);
	}
	~CProcLock()
	{
		// 在析构函数中,对文件锁进行尝试解锁,并关闭已打开的文件描述符,以及设初始化状态为false
		for (unsigned i = 0U; i < PROC_LOCK_MAX_NUM; i++)
		{
			::flock(m_fds[i], LOCK_UN);
			if (m_fds[i] >= 0)
			{
				::close(m_fds[i]);
				m_fds[i] = -1;
			}
		}
		m_is_init = false;
	}

	Result lock(const ProcLock::Type type) // 写锁
	{
		if (ProcLock::Type::MAX_NUMS == type) // 类型错误
		{
			return Result::PARA_ERROR;
		}
		if (m_is_init != true) // 进程锁为初始化
		{
			return Result::FILE_LCOK_UN_INIT;
		}
		// 将枚举转换为对应的数组下标,以获得对应的文件描述符和mutex
		const unsigned type_num = static_cast<unsigned>(type); 
		// 先保证线程安全,对应的线程读写锁加写锁
        m_rw_mtxs[type_num].lock(); 
        // 对文件进行加锁
		const int ret = ::flock(m_fds[type_num], LOCK_EX);
	
		return (ret != 0 ? Result::FILE_LOCK_FAIL : Result::OK);
	}
	Result shared_lock(const ProcLock::Type type)
	{
		if (ProcLock::Type::MAX_NUMS == type)
		{
			return Result::PARA_ERROR;
		}
		if (m_is_init != true)
		{
			return Result::FILE_LCOK_UN_INIT;
		}
		int ret = 0;
		const unsigned type_num = static_cast<unsigned>(type);
		// 先保证线程安全,对应的线程读写锁加读锁
        m_rw_mtxs[type_num].lock_shared();
        // 对应的文件锁的互斥锁加锁,目的是保证持有读锁的计数器线程安全
        m_shard_nums_mtxs[type_num].lock();   
        // 当进程读锁计数器数目为0时,对文件加读锁   
        if (0 == m_shared_lock_nums[type_num])
		{
			ret = ::flock(m_fds[type_num], LOCK_SH);
		}
		// 进程读锁计数器数目++
		m_shared_lock_nums[type_num]++;
		// 解开互斥锁
        m_shard_nums_mtxs[type_num].unlock();      
		return (ret != 0 ? Result::FILE_LOCK_FAIL : Result::OK);
	}
	Result unlock(const ProcLock::Type type)
	{
		if (ProcLock::Type::MAX_NUMS == type)
		{
			return Result::PARA_ERROR;
		}
		if (m_is_init != true)
		{
			return Result::FILE_LCOK_UN_INIT;
		}
		const unsigned type_num = static_cast<unsigned>(type);
		// 解开文件锁
        const int ret = ::flock(m_fds[type_num], LOCK_UN);
        // 解开文件锁后再解开对应的线程读写锁的写锁
        m_rw_mtxs[type_num].unlock();
		return (ret != 0 ? Result::FILE_LOCK_FAIL : Result::OK);
	}
    Result shared_unlock(const ProcLock::Type type)
	{
		if (ProcLock::Type::MAX_NUMS == type)
		{
			return Result::PARA_ERROR;
		}
		if (m_is_init != true)
		{
			return Result::FILE_LCOK_UN_INIT;
		}
		int ret = 0;
		const unsigned type_num = static_cast<unsigned>(type);
		// 对应的文件锁的互斥锁加锁,目的是保证持有读锁的计数器线程安全
        m_shard_nums_mtxs[type_num].lock();   
        // 进程读锁计数器--   
		if (m_shared_lock_nums[type_num] > 0)
		{
			m_shared_lock_nums[type_num]--;
		}
		// 当进程读锁计数器为0时,对文件进行解锁
		if (0 == m_shared_lock_nums[type_num])
		{
			ret = ::flock(m_fds[type_num], LOCK_UN);
		}
		// 解开互斥锁
        m_shard_nums_mtxs[type_num].unlock();
        // 解开文件锁后再解开对应的线程读写锁的读锁      
        m_rw_mtxs[type_num].unlock_shared();
		return (ret != 0 ? Result::FILE_LOCK_FAIL : Result::OK);
	}
};

// 这里是为了实现为单例模式的
// 这也是我为什么把类声明定义在源文件中而非头文件中
static CProcLock g_PROC_LOCK;

Result ProcLock::Lock(const ProcLock::Type type)
{
	return g_PROC_LOCK.lock(type);
}

Result ProcLock::UnLock(const ProcLock::Type type)
{
	return g_PROC_LOCK.unlock(type);
}

Result ProcLock::SharedLock(const ProcLock::Type type)
{
	return g_PROC_LOCK.shared_lock(type);
}

Result ProcLock::SharedUnLock(const ProcLock::Type type)
{
	return g_PROC_LOCK.shared_unlock(type);
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值