Linux系统编程之线程同步

本章节着重介绍多线程下资源同步的问题,之前章节介绍了Linux多线程编程的有关知识,由于多线程下容易产生资源互抢情况,所以必须对共享资源进行统一规划,其涉及到的概念有互斥量,条件变量,信号量,文件锁以及进程间锁。

线程同步

多线程下共享资源的争夺很是普遍的,对于为什么要进行线程同步,有如下几点:

  1. 共享资源,多个线程都可对共享资源操作
  2. 线程操作共享资源的先后顺序不确定
  3. 处理器对存储器的操作一般不是原子操作

互斥量 pthread_mutex_t

在多线程中,当多个线程访问同一个共享资源时候,这时候系统提供一把锁和一个钥匙,当线程A请求访问公共资源,那么线程A就申请系统拿到钥匙并上锁,此时如果另外一个线程B也想访问这个共享资源的话,也要向系统申请钥匙开锁,此时系统发现锁已经被线程A持有,就告诉线程B等待线程A访问完成后,锁被归还再通知线程B可以拿钥匙上锁,继而访问公共资源。

针对以上模型,互斥量就是这么个概念。可以把互斥变量值置为常量PTHREAD_MUTEX_INITIALIZER(针对静态分配的互斥量),或调用pthread_mutex_init函数进行初始化。如果动态的分配互斥量(如调用malloc函数),那么在释放内存前需要调用pthread_mutex_destory。

操作原语
// 初始化,动态初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
// 动态初始化后,手动回收
int pthread_mutex_destroy(pthread_mutex_t *mutex)

// 拿钥匙上锁,如果拿到钥匙,锁就被持有,外部再调用拿钥匙访问就会阻塞等待
int pthread_mutex_tlock(pthread_mutex_t *mutex)
// 尝试拿钥匙上锁,如果已经被锁,直接返回EBUSY,不阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex)
// 释放锁资源
int pthread_mutex_unlock(pthreadd_mutex_t *mutex)

对于锁的初始化有两种方式,静态和动态,如下:

  1. 静态,直接使用常量 PTHREAD_MUTEX_INITIALIZER 初始化

    // 锁静态声明定义
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    pthread_mutex_lock(&mutex);
    //do someting
    pthread_mutex_unlock(&mutex);
    
  2. 动态初始化

    pthread_mutex_t mutex;
    // 默认方式初始化 mutex,属性值 NULL
    pthread_mutex_init(&mutex, NULL);
    
    pthread_mutex_lock(&mutex);
    //do someting
    pthread_mutex_unlock(&mutex);
    
    // 释放锁资源
    pthread_mutex_destroy(&mutex)
    
临界区

保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

临界区的选定

临界区的选定因尽可能小,如果选定太大会影响程序的并行处理性能。
死锁

在使用互斥量时候,要避免死锁的发生,死锁产生原因:

  1. 同一个线程在拥有A锁的情况下再次请求获得A锁
  2. 线程一拥有A锁,请求获得B锁;线程二拥有B锁,请求获得A锁

避免发生死锁的方法:

  1. 按顺序加锁,例如加锁 lock1, lock2,lock3,在加锁 lock3 之前,必须先加 lock2 ,以此类推。释放 lock 时必须按倒序来
  2. 如果无法确定锁的顺序,尽量用 pthread_mutex_trylock 代替 pthread_mutex_lock 以避免死锁
  3. 用串行代替并行
实例代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

/*
    互斥量
    同一时间为 1 或者 0
    保持共享数据的完整正确性
*/

#define NLOOP 5000

// 共享数据
int counter;

// 锁静态声明定义
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

void *pthread_do(void * arg){
    int i, val;
    for (int i = 0; i < NLOOP; ++i)
    {
        // 加锁,否则数据值丢失一半
        pthread_mutex_lock(&counter_mutex);
        val = counter;
        printf("%x: %d\n", (unsigned int)pthread_self(), val + 1);
        counter = val + 1;
        // 解除锁
        pthread_mutex_unlock(&counter_mutex);
    }
    return NULL;
}

int main(int argc, int *argv[]){

    pthread_t tidA,tidB;
    // 创建两个线程,争抢共享资源,通过互斥量保持数据的正确性
    pthread_create(&tidA,NULL,pthread_do,NULL);
    pthread_create(&tidB,NULL,pthread_do,NULL);

    // 回收线程数据
    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);

    return 0;
}

读写锁 pthread_rwlock_t

百度百科如下解释读写锁:

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
读写锁原语
// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
// 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
// 获取读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
// 获取写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
// 解锁(读锁后调用解读锁,写锁同样)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
// 尝试拿读锁,非阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
// 尝试拿写锁,非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)

读写锁机制:读共享,写独占

代码实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

/*
    读写锁
    读时共享
    写时独占
*/

int counter;
// 定义读写锁
pthread_rwlock_t rwlock;

// 读线程,读共享
void *th_read(void *arg){
    int i = 100;
    while(i--){
        // 获取读锁
        pthread_rwlock_rdlock(&rwlock);
        printf("read: %x, %d \n",pthread_self(),counter);
        // 解除读锁
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }
}

// 写线程,写独占
void *th_write(void *arg){
    int t;
    int i = 100;
    while(i--){
        // 获取写锁
        pthread_rwlock_wrlock(&rwlock);
        t = counter;
        // usleep(100);
        printf("write: %x, counter=%d, ++counter=%d \n",pthread_self(), t,++counter );
        // 解除写锁
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }
}

int main(int argc, char *argv[]){
    pthread_t tid[8];
    // 初始化读写锁
    pthread_rwlock_init(&rwlock,NULL);
    // 3线程写
    for (int i = 0; i < 3; ++i)
    {
        pthread_create(&tid[i],NULL,th_write,NULL);
    }
    // 5线程读
    for (int i = 0; i < 5; ++i)
    {
        pthread_create(&tid[i+3],NULL,th_read,NULL);
    }
    // 销毁读写锁
    pthread_rwlock_destroy(&rwlock);

    for (int i = 0; i < 8; ++i)
    {
        /* code */
        // 回收线程资源
        pthread_join(tid[i],NULL);
    }
    return 0;
}

条件变量 pthread_cond_t

条件变量是利用线程间共享全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。

条件变量一般和互斥量同步使用,其给多个线程提供了一个汇合的场所。例如典型的生产者和消费者,条件变量作为有产品的先决条件,而产品本身就可以看作互斥量,因为消费者也可能是多个线程同时争抢生产者生产出来的产品。

条件变量原语

初始化条件变量
  
int pthread_cond_init(pthread_cond_t cond,pthread_condattr_t cond_attr)

尽管POSIX标准中为条件变量定义了属性,但在Linux中没有实现,因此cond_attr值通常为NULL,且被忽略。

有两个等待函数

// 无条件等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)
// 超时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

 
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求。mutex互斥锁在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

激发条件

// 激活一个等待该条件的线程(存在多个等待线程时按入队顺序激活其中一个)  
int pthread_cond_signal(pthread_cond_t *cond);
// 激活所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond); 

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

只有在没有线程在该条件变量上等待的时候才能销毁这个条件变量,否则返回EBUSY

说明:

  1. pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。

  2. 互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。

  3. 条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁

参考链接

代码实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

/*
    条件变量
    条件变量给多个线程提供了一个汇合的场所
    生产与消费模式
*/

// 产品信息
struct msg
{
    struct msg *next;
    int num;
};

struct msg *head;
// 存在生产条件变量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;

// 互斥量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;;

// 消费者
void *consumer(void *arg){
    struct msg *msg;
    for(;;){
        // 先加锁,访问共享资源head,如果为空,则进入等待条件变量
        pthread_mutex_lock(&lock);
        // 阻塞等待生产条件变量存在,此时还是加锁状态
        while(head == NULL)
            // 进入阻塞等待条件变量,此时线程挂起进入等待,锁被解开,生产者可以获取并生产
            pthread_cond_wait(&has_product,&lock);

        msg = head;
        head = msg->next;
        // 获取产品信息后,解锁以避免别的线程争抢资源
        pthread_mutex_unlock(&lock);

        printf("Consume %d \n",msg->num);
        free(msg);
        sleep(rand()%5);
    }
}

// 生产者
void *producer(void *arg){
    struct msg *msg;
    for (;;)
    {
        // 新建产品内容
        msg = malloc(sizeof(struct msg));
        msg->num = rand() %1000 + 1;
        printf("Produce %d \n",msg->num);

        // 上锁,生产产品
        pthread_mutex_lock(&lock);
        msg->next = head;
        head = msg;
        // 解锁,生产完成
        pthread_mutex_unlock(&lock);
        // 传递生产条件变量到消费者,进行条件变量通知
        pthread_cond_signal(&has_product);
        sleep(rand() % 5);
    }
}

int main(int argc, char *argv[]){

    pthread_t tidA,tidB;

    // 利用系统时间产生随机数种子值,生产随机数
    srand(time(NULL));

    // 创建两个线程,测试消费和生产之间资源传递关系
    pthread_create(&tidA,NULL,producer,NULL);
    pthread_create(&tidB,NULL,consumer,NULL);

    // 回收线程资源
    pthread_join(tidA,NULL);
    pthread_join(tidB,NULL);

    return 0;
}

在消费者中,需要注意一下情况:

  1. 消费者在消费之前,需要上锁获取共享资源生产的产品,此时产品在消费者线程中处于被锁状态,生产者此时无法获取,只能阻塞。当消费者监测无生产产品是,死循环等待条件变量 pthread_cond_wait 的产生。
  2. pthread_cond_wait 进入之前,原语保证必须本线程处于锁状态中,此时线程进入挂起等待时刻,锁被解开,其他线程可以访问生产产品。
  3. 当生产者拿到锁生产处产品后,在调用 pthread_cond_signal 通知条件变量变化时,必须解锁共享产品资源,因为一旦通知后,消费者线程中就立即对生产产品的锁状态进行恢复,以确保代码往下继续进行。

信号量 sem_t

信号量原理上等同于多个互斥量,同时提供多把锁和钥匙,丰富多线程下共享资源访问的并发行。

信号量原语

头文件

#include <semaphore.h>

初始化信号量

int sem_init (sem_t *sem , int pshared, unsigned int value)

sem - 指定要初始化的信号量
pshared - 信号量 sem 的共享选项,linux只支持0,表示它是当前进程的局部信号量
value - 信号量 sem 的初始值

信号量值加1

int sem_post(sem_t *sem)

给参数sem指定的信号量值加1

信号量值减1

int sem_wait(sem_t *sem)

给参数sem指定的信号量值减1,如果sem所指的信号量的数值为0,函数将会阻塞等待直到有其它线程使它不再是0为止

销毁信号量

int sem_destroy(sem_t *sem)

销毁指定的信号量
代码实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

/*
    信号量,互斥量的升级版,同时多把锁
*/

// 数据内容大小
#define NUM 5
// 队列 5
int queue[NUM];
// 信号量
sem_t blank_number, product_number;
// 生产者
void *producer(void *arg){
    int n = 0;
    while(1){
        // 取锁,blank_number内锁自减一;为0则阻塞等待
        sem_wait(&blank_number);
        queue[n] = rand() % 1000 + 1;
        printf("Produce %d num:%d \n",n,queue[n]);
        // 释放锁,blank_number内锁自加一
        sem_post(&product_number);
        // 队列数据补足,形成环形队列
        n = (n+1) % NUM;
        sleep(rand() % 5);
    }
}

// 消费者
void *consumer(void *arg){
    int n = 0;
    while(1){
        sem_wait(&product_number);
        printf("Consume %d num: %d\n",n,queue[n]);
        queue[n] = 0;
        sem_post(&blank_number);
        n = (n+1) % NUM;
        sleep(rand() % 5);
    }
}

int main(int argc, char *argv[]){

    pthread_t tidA, tidB;
    // 初始化信号量,
    // blank_number: 信号量数值5, 0等同于互斥量(存在一个锁)
    // 中间参数: 0 代表的是局部线程间共享信号量
    sem_init(&blank_number, 0, NUM);
    sem_init(&product_number, 0, 0);

    // 两个线程间调度生产与消费内容
    pthread_create(&tidA, NULL, producer, NULL);
    pthread_create(&tidB, NULL, consumer, NULL);

    // 回收线程资源
    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);

    // 销毁信号量
    sem_destroy(&blank_number);
    sem_destroy(&product_number);
    return 0;
}

相关解释:

  1. 生产者持有的信号量5个,容器数组正好有5个缓冲区。每次生产调用sem_wait,信号量值自减少1(为0阻塞等待),生产完成调用sem_post释放。也就是同时可以5个生产者并发生产。
  2. 消费者对于数据的读取串行处理,其信号量值为0,等同于互斥量。数组内容一次读取,通过取余数进行矫正位。
  3. 消费者中对于信号量(初始值0,相当于互斥量,只能进入一次)首次调用sem_wait进入处理,此时信号量为0,其他线程再次获取及阻塞等待,获取数组内数据后,调用sem_post进行释放。

进程间锁 pthread_mutex_attr_t

进程间共享内存互斥量,可以通过 pthread_mutex_attr_t 来配置。通过 pthread_mutex_attr_t 可以让进程之间访问共享的互斥量,从而进行资源的有序调度。

共享互斥量操作原语

初始化共享互斥量

pthread_mutexattr_init(pthread_mutex_attr_t *attr)

操作共享互斥量属性

// 获得共享互斥量属性,由shared带出
intpthread_mutexattr_getpshared(const pthread_mutexattr_t *restrictattr, int *restrictshared );

// 设置共享互斥属性,有shard决定,默认为PTHREAD_PROCESS_PRIVATE,即线程共享
intpthread_mutexattrattr_ setpshared (  constpthread_mutexattr_t *restrict attr,int pshared);

若成功返回0,若失败返回错误编号。
注意:shared的取值可以是
        PTHREAD_PROCESS_SHARED  存在共享内存中,可以被多个进程中的线程共享
        PTHREAD_PROCESS_PRIVATE 只有和创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁

销毁共享互斥量

pthread_mutex_attr_t_destroy(pthread_mutex_attr_t *attr)
代码实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

/*
    自定义进程间锁结构体
*/
struct mutex_lock
{
    int num;
    // 互斥量
    pthread_mutex_t mutex;
    // 共享互斥量属性
    pthread_mutexattr_t mutexattr;
};

int main(void){
    int fd ,i;
    struct mutex_lock *mlock;
    pid_t pid;
    fd = open("temp",O_CREAT | O_RDWR, 0777);
    // 拓展文件大小
    ftruncate(fd,sizeof(*mlock));
    // 内存映射,进程间读写数据
    mlock = mmap(NULL,sizeof(*mlock), PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
    close(fd);
    // 清空内存
    memset(mlock,0,sizeof(*mutex_lock));

    // 初始化进程间锁属性
    pthread_mutexattr_init(&mlock->mutexattr);
    // 设置进程间锁为进程间共享(默认为线程共享)
    pthread_mutexattr_setpshared(&mlock->mutexattr, PTHREAD_PROCESS_SHARED);
    // 初始化锁
    pthread_mutex_init(&mlock->mutex,&mlock->mutexattr);

    pid = fork();
    // 通过父子不同进程,共同访问共享互斥量中 num 数据,对其进行修改
    if(pid == 0){
        // 子进程,临界区内执行10次 +1 操作
        for (int i = 0; i < 10; ++i)
        {
            // 取锁
            pthread_mutex_lock(&mlock->mutex);
            (mlock->num)++;
            printf("Child process num: %d\n",mlock->num);
            // 释放锁
            pthread_mutex_unlock(&mlock->mutex);
            sleep(1);
        }
    }
    else {
        for (int i = 0; i < 10; ++i)
        {
            // 父进程,临界区内执行10次 +2 操作
            pthread_mutex_lock(&mlock->mutex);
            mlock->num += 2;
            printf("Parent process num: %d\n",mlock->num);
            pthread_mutex_unlock(&mlock->mutex);
            sleep(1);
        }
        // 主线程阻塞等待子线程处理完毕
        wait(NULL);
    }

    // 释放锁资源
    pthread_mutexattr_destroy(&mlock->mutexattr);
    pthread_mutex_destroy(&mlock->mutex);

    // 解除内存文件映射
    munmap(mlock,sizeof(*mlock));
    // 删除临时文件
    unlink("temp");
    return 0;
}

代码说明:

  1. 自定义结构体 mutex_lock 提供不同进程访问的数据基础。
  2. 初始化共享互斥量时候,通过内存映射将 mutex_lock 结构体置位进程共享(MAP_SHARED),同时其内部互斥量属性设置为进程共享(PTHREAD_PROCESS_SHARED)
  3. 父子进程访问结构体 mutex_lock 中的 num 时,都进行上锁,修改数据,解锁等操作,其互斥量能够进程间访问,依赖于之前内存映射以及互斥量属性中的都进行了进程共享属性设置。

文件锁 fcntl

文件锁是用于解决资源的共享使用的一种机制:当多个用户需要共享一个文件时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。

之前在文件I/O章节中介绍过这个函数,当时只是提到了如何使用 fcntl 进行对一大文件描述符进行权限位的更改,现在我们来了解如何通过 fcntl 对文件进行加锁访问。

文件锁机制中同样采用的是读共享,写排斥。

再了解 fcntl 函数之前,先来了解一下结构体 flock

struct flock {
... 
short l_type; // 文件锁操作类型: F_RDLCK(读锁),F_WRLCK(写锁), F_UNLCK(解锁)
short l_whence; // 上锁内容的规定: SEEK_SET(开始), SEEK_CUR(游标当前), SEEK_END(结尾 
off_t l_start; // 文件锁内容开始位置
off_t l_len; // 文件锁区域长度,0代表全部 
pid_t l_pid; //获取上锁进程pid(仅对F_GETLK有效)
...        
}; 
fcntl 原语
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ )

例如: int ret = fcntl(fd, F_SETLKW, &lock);

cmd命令解释:

  1. F_SETLK:设置锁(读锁F_RDLCK,写锁F_WRLCK)或者释放所(F_UNLCK),如果无法获取,直接返回error。
  2. F_SETLKW:功能和F_SETLK一样,区别是阻塞等待
  3. F_GETLK:这个接口是获取锁的相关信息:这个接口会修改我们传入的struct flock

通过函数参数功能可以看出 fcntl(相对于lockf)是功能最强大的,它既支持共享锁又支持排他锁,即可以锁住整个文件,又能只锁文件的某一部分。

操作方式,通过结构体指定锁文件内容区域以及锁方式,然后通过命令 fcntl 将结构体锁属性提交到指定文件中。

实例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

/*
    文件锁
    通过结构体 struct flock 来设置文件锁的具体属性,通过fcntl提交文件锁到文件
    读锁共享,写锁互斥
*/

void sys_err(char *err){
    perror(err);
    exit(1);
}

int main(int argc, char *argv[]){

    int fd;
    struct flock f_lock;
    if(argc < 2){
        printf(" ./app must need filename\n");
        exit(1);
    }

    if((fd = open(argv[1],O_RDWR))<0){
        sys_err("open");
    }

    // 写入文件锁,互斥
    f_lock.l_type = F_WRLCK;
    // 读文件锁,共享
    // f_lock.l_type = F_RDLCK;
    // 文件锁的游标指针
    f_lock.l_whence = SEEK_SET;
    // 文件锁的锁区开始位置
    f_lock.l_start = 0;
    // 文件锁锁区的大小,0代表全部锁住
    f_lock.l_len = 0;

    // 提交文件锁属性到文件
    fcntl(fd,F_SETLKW, &f_lock);
    printf("lock file! \n");
    sleep(30);

    // 解除文件锁
    f_lock.l_type = F_UNLCK;
    fcntl(fd,F_SETLKW, &f_lock);
    printf("unlock flile! \n");

    close(fd);
    return 0;

}

以上代码可以采用以下方式测试:

  1. 读共享

    注释 f_lock.l_type = F_WRLCK;
    打开 f_lock.l_type = F_RDLCK;
    新建txt文件到程序目录下,其内容自定义
    运行程序 ./app .txt
    程序运行期间(30s),新建并运行程序对
    .txt 进行内容读取,测试是否成功

  2. 写排斥

    打开 f_lock.l_type = F_WRLCK;
    注释 f_lock.l_type = F_RDLCK;
    新建txt文件到程序目录下,其内容自定义
    运行程序 ./app .txt
    程序运行期间(30s),新建并运行程序对
    .txt 进行内容修改,测试是否成功

总结

本博文主要介绍了如何对多线程下产生各种资源同步问题应对方式,主要集中介绍了互斥量,条件变量,信号量,文件锁以及进程间共享锁集中处理方式。

从上面的分析中,我们可以得出:

  1. 单一的线程同步可以产用互斥量,条件变量和互斥量一同使用,可以控制共享资源的访问序列。
  2. 信号量机制提供了多把锁,进一步提升多线程下共享资源访问的并发性。
  3. 对于进程间同步,可以采用共享互斥量方式,通过设置互斥量属性实现,其机制上仍需要共享内存的帮助。
  4. 文件锁可以对文件进行锁同步,机制上依旧是读共享,写排斥方式。

邢文鹏Linux教学资料

坚持原创技术分享,您的支持将鼓励我继续创作!