Linux锁机制


Linux下开发,可能会遇到多个进程或线程在同一时间对相同文件或变量或结构体进行写操作,如果使用操作不当,可能会造成文件损坏或者程序运行结果与预期不一致的现象,那么应该如何正确处理呢?

一、针对变量或结构体加锁

一般来说,多线程同时修改共享变量时会出现数据不一致的问题,比如多个线程同时对一个变量加1,假设count的初始值为0:

int count = 0;

void add() 
{
    
    
    ++count;
}

如果只有一个线程调用add函数,那么什么问题都没有,但如果多个线程同时调用上述函数,比如10个线程都调用一遍,那么count值最后不一定等于10,原因在于对count加1的操作不是原子的。

所谓原子操作,是指CPU要么执行该操作,要么不执行该操作,不存在中间状态,但上述对count加1的操作经过编译器处理后会生成几条对应的机器指令,所以该操作不是原子的。

那么怎样才能让加1操作变成原子的呢?很简单,加一把锁。

int count = 0;
mutex mtx; // 锁

void add() 
{
    
    
    mtx.lock();
    ++count;
    mtx.unlock();
};

现在我们用一把锁将对count的操作保护了起来,此时你可以将mtx.lock()以及mtx.unlock()中间的代码看成原子的,CPU要完全执行完对count的加1要么根本不会操作count,这样上述程序的运行结果就是我们想要的了。

CPU中有特定的原子指令,用户态也可以使用这些指令,因此我们可以将上述代码进行优化:

int count = 0;
               
void add() 
{
    
    
    int old_value;
    do
    {
    
    
		old_value = count;
    } while (!atomic_compare_exchange(&count, &old_value, old_value + 1));
}

此时add函数是线程安全的,我们也没有对其进行加锁,不管多少线程同时调用add函数得到count都是正确的,而该函数的执行完全不涉及操作系统,不需要操作系统来维护秩序,利用的就是CPU中的原子指令,CPU在硬件层面一样可以替我们维护秩序

二、文件锁

2.1 文件锁的分类

2.1.1 建议性锁

建议性锁的用法是程序访问文件之前,先对文件上锁, 上锁成功之后再访问文件,但是如果文件没有上锁,程序也是可以访问文件

2.1.2 强制性锁

如果进程对文件上了强制性锁,其它的进程在没有获取到文件锁的情况下是无法对文件进行访问

2.2 flock函数

头文件: #include<sys/file.h>
函数定义: int flock(int fd, int operation);
函数说明:flock()会根据参数 operation 所指定的方式对参数 fd 所指的文件做各种锁定或解除锁定的动作
备注:flock()函数只能锁定整个文件,无法锁定文件的某一区域
参数定义:operation有下列四种情况:

1、LOCK_SH:建立共享锁定。多个进程可同时对同一个文件作共享锁定
2、LOCK_EX:建立互斥锁定。一个文件同时只有一个互斥锁定
3、LOCK_UN:解除文件锁定状态
4、LOCK_NB:无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合

返回值:返回0表示成功,若有错误则返回-1,错误代码存于errno

案例:

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

int main()
{
    
    
	FILE *fd;
	char str[12] = "123456";
	char fileName[12] = "1.txt";
	//读写打开一个文本文件,允许读和写
	fd = fopen(fileName, "rt+");
	if(!fd)
	{
    
    
		printf("Fail to open the file\n");
		return -1;
	}

	//建立互斥锁,阻塞方式
	if((flock(fileno(fd), LOCK_EX)) < 0 )
	{
    
    
		printf("Fail to lock the file\n");
		return -1;
	}
	
	//写文件
	fwrite(str, sizeof(str), 1, fd);

	//解除锁
	if((flock(fileno(fin), LOCK_UN)) < 0 )
	{
    
    
		printf("Fail to unlock the file);
		return -1;
	}
	
	fclose(fd);
	
	return 0;
}

2.3 fcntl函数

头文件:#include<fcntl.h>
函数定义: int fcntl(int fd,int cmd,struct flock *lock);
函数说明:可以对已打开的文件描述符进行各种操作,包括:

 1. 管理文件锁 
 2. 获得设置文件描述符和文件描述符标志 
 3. 文件描述符的复制等

参数定义:
fd:文件描述符
lock:flock结构体,记录锁的具体状态

struct flock
{
    
    
	short l_type;
	off_t l_start;
	short l_whence;
	off_t l_len;
	pid_t l_pid;
}

cmd:命令参数,包括

F_DUPFD:复制文件描述符 
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec函数之后仍保存打开状态 
F_SETFD:设置close-on-exec标志,该标志由参数arg的FD_CLOEXEC位决定 
F_GETFL:得到open设置的标志 
F_SETFL:改变open设置的标志 
F_GETLK:根据lock描述,决定是否上文件锁 F_SETLK:设置lock描述的文件锁 
F_SETLKW:F_SETLK的阻塞版本(命令名中的W表示等待)。在无法获取锁时,进入睡眠状态;如果可以获取锁或者捕捉信号则返回 

返回值:返回0表示成功,若有错误则返回-1,错误代码存于errno

猜你喜欢

转载自blog.csdn.net/future_sky_word/article/details/131489355