Linux锁机制

flock、lockf和fcntl

Linux的文件锁主要有两种:flock和lockf。lockf只是fcntl系统调用的一个封装。

lockf或fcntl实现了更细粒度文件锁,即记录锁,可以对文件的部分字节上锁,而flock只能对整个文件加锁。

应该避免在多线程场景下使用flock对文件加锁,而lockf/fcntl则没有这个问题。

建议锁和强制锁

建议锁:如果某一个进程对一个文件持有一把锁之后,其他进程仍然可以直接对文件进行操作的。只是一种编程上的约定。

强制锁:试图实现一套内核级的锁操作。当有进程对某个文件上锁之后,其他进程即使不在操作文件之前检查锁,也会在open、read或write等文件操作时发生错误。

flock和lockf都是建议锁。

fcntl系统调用可以支持强制锁。

线程锁

多线程共享地址空间在方便我们编程的同时,也带来了一些麻烦,其中就包括共享资源的争抢问题.

互斥锁(pthread_mutex_t)。当一个线程拿到锁的时候,其他线程运行到这里的时候会被挂起阻塞,等待锁释放后再唤醒运行。也可用作进程间同步,不过需要利用共享内存。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

自旋锁(pthread_spinlock_t)。没抢到锁的线程不会挂起,而是不断的检查锁是否被释放,其原理是内部保存了一个原子变量,加锁和解锁就是对原子变量的增减,被阻塞的线程不断检查这个变量的值,决定是否继续忙等。由于用户态程序无法控制线程调度,所以可能出现一个线程刚刚拿到了锁就被切换了出去,造成另一个线程在整个时间片内自旋,白白浪费cpu资源。

读写锁(pthread_rwlock_t)。读锁可以被多个线程持有,而同时只能有一个写线程,且不能同时存在读写操作。读多写少的场合用读写锁可以提高并发性,但读写锁的实现本身就要维护一个原子的读者数量,如果锁的粒度比较小,锁本身的消耗就不可忽略了。

读写锁加解锁的代价比普通锁大,但是依然有其使用场景:

少量操作需要写锁,多个线程需要读锁,并且持有读锁的时间比较长(显著高于加解锁的耗时)那么用读写锁能够明显提高并发度。

条件变量(pthread_cond_t)。与pthread_mutex_t配合,让线程睡眠,不占用CPU,直到事件发生(pthread_cond_broadcast、pthread_cond_signal)为止。也可用作进程间同步,不过需要利用共享内存。

在使用pthread_cond_wait函数之前,我们要先用一个互斥锁锁住,然后当我们调用pthread_cond_wait函数进入睡眠。该函数原子的执行两个动作:

1,给互斥锁解锁。(这就要求在调用这个函数之前要上锁)

2,把调用线程投入睡眠。

一旦这个函数被唤醒后,那么在此函数返回前重新给互斥锁上锁。这也就决定了在接下来的程序里必须有解锁的步骤。

#include <pthread.h>  
#include <stdio.h>  
#include <stdlib.h>  
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/  
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/  
void *thread1(void *);  
void *thread2(void *);  
int i=1;  
int main(void)  {  
    pthread_t t_a;  
    pthread_t t_b;  
    pthread_create(&t_a,NULL,thread1,(void *)NULL);/*创建进程t_a*/  
    pthread_create(&t_b,NULL,thread2,(void *)NULL); /*创建进程t_b*/  
    pthread_join(t_a, NULL);/*等待进程t_a结束*/  
    pthread_join(t_b, NULL);/*等待进程t_b结束*/  
    pthread_mutex_destroy(&mutex);  
    pthread_cond_destroy(&cond);  
    exit(0);  
}  

void *thread1(void *junk)  {  
    for(i=1;i<=6;i++){  
        pthread_mutex_lock(&mutex);/*锁住互斥量*/  
        if(i%3==0){  
            printf("thread1:signal 1  %d/n", __LINE__);  
            pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/  
            printf("thread1:signal 2  %d/n", __LINE__);  
            sleep(1);  
        }  
        pthread_mutex_unlock(&mutex);/*解锁互斥量*/  
        sleep(1);  
    }  
}  

void *thread2(void *junk)  {  
    while(i<6){  
        pthread_mutex_lock(&mutex);   
        if(i%3!=0){  
            printf("thread2: wait 1  %d/n", __LINE__);  
            pthread_cond_wait(&cond,&mutex);/*解锁mutex,并等待cond改变*/  
            printf("thread2: wait 2  %d/n", __LINE__);  
        }  
        pthread_mutex_unlock(&mutex);  
        sleep(1);  
    }  
}  

如果信号处理函数打断了pthread_cond_wait(),该函数要么自动重新自行(linux是这样实现的),或者返回0(这时应用要检查返回值,判断是否为假唤醒)

信号量:

信号量集:信号量机制解决了单个资源的互斥访问,信号量集解决同时需要多个资源时的互斥访问。

脚本

-n参数可以让flock命令以非阻塞方式探测一个文件是否已经被加锁。

脚本退出的时候锁会被释放,所以这里可以不用显式的使用flock解锁。

*/1 * * * * /usr/bin/flock -xn/tmp/script.lock -c '/home/bash/script.sh'


如果文章对您有帮助,欢迎扫描下方二维码赞助(一分也是爱噢),谢谢

Search

    一分也是爱噢 一分也是爱

    目录