使用信号量控制线程互斥和同步。

首先要知道PV原语的概念。以下是百度百科对PV原语的介绍:

PV原语通过操作信号量来处理进程间的同步与互斥的问题。其核心就是一段不可分割不可中断的程序。

信号量的概念1965年由著名的荷兰计算机科学家Dijkstra提出,其基本思路是用一种新的变量类型(semaphore)来记录当前可用资源的数量。有两种实现方式:1)semaphore的取值必须大于或等于0。0表示当前已没有空闲资源,而正数表示当前空闲资源的数量;2) semaphore的取值可正可负,负数的绝对值表示正在等待进入临界区的进程个数。

信号量是由操作系统来维护的,用户进程只能通过初始化和两个标准原语(P、V原语)来访问。初始化可指定一个非负整数,即空闲资源总数。

P原语:P是荷兰语Proberen(测试)的首字母。为阻塞原语,负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V原语:V是荷兰语Verhogen(增加)的首字母。为唤醒原语,负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。


可以看出其实是使用PV操作信号量,然后根据信号量的数值决定该线程是堵塞或者执行。

Linux实现了POSIX的无名信号量。常用的函数有以下几个:

  • sem_init:用来创建一个信号量,并初始化它的值。

  • sem_wait和sem_trywait:相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。

  • sem_post:相当于是V操作,它将信号量的值加一同时发出信号唤醒等待的进程。

  • sem_getvalue:用于得到信号量的值。
  • sem_destroy:用于删除信号量。

其中sem_init函数所需要的头文件是:#include < semaphore.h>,其他的函数所需要的头文件是:#include < pthread.h>

sem_init函数原型如下:

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

各个参数的含义如下:
- sem:信号量。
- pshared:决定信号量能否在几个进程间共享。
- value:信号量的初始值。

其他的函数比如:sem_wait(sem_t* sem)都是有一个参数:sem_t* sem,也就是所要操作的信号量。

这些函数调用成功的话返回0,失败返回-1.


互斥

如果要用信号量来控制线程的互斥的话,那么就可以使用sem_init初始化一个值为1的信号量,然后两个线程分别使用sem_wait和sem_post对该信号量进行增加或者减少。如果线程一先使用sem_wait的话,那么此时信号量的值就是0了,线程二执行sem_wait之后发现值为-1根据P原语就会将这个线程阻塞,直到线程一执行sem_post将信号量的值增加一等于0并且唤醒等待的线程二,执行线程二的代码。而此时如果线程一再次执行sem_wait的话信号量的值又成了-1,线程一就被挂起了,知道线程二执行sem_post之后,信号量值为0并唤醒线程一。

大致流程如下:

使用信号量控制线程互斥和同步。_第1张图片

线程一或者线程二无论谁先调用sem_wait就相当于谁先使用了共享资源,那么另一个线程只能进行阻塞等待。

同步

如果用信号量控制线程同步的话,那么只需要设置两个信号量,然后根据初始化值的不同交叉执行就可。比如sem1和sem2两个信号量,sem1的初始化值为1,sem2的初始化值为0。那么线程一使用sem_wait操作sem1,这时候sem1的值为0并且执行线程一的代码(因为sem大于等于0都可以),而此时如果线程二执行sem_wait的话是操作sem2的,这时候sem2的值为-1所以线程二阻塞。等到线程一使用sem_post操作sem2的时候,线程二sem2的值就成了0并且被唤醒执行线程二了。而此时线程一如果再次执行sem_wait的话sem1的值就是-1了,只有等到线程二执行sem_post操作sem1的值将其变为0才可以执行线程一。

大致流程如下:

使用信号量控制线程互斥和同步。_第2张图片
两个sem信号量被两个线程交叉操作,线程一P原语操作sem2,V原语操作sem1。线程二P原语操作sem1,V原语操作sem2。这样就可以让两个线程依次顺序同步的执行下去。而谁先执行就将其信号量初始化为1,另一个初始化为0即可。


下面是使用信号量来完成线程的互斥:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int lock_var;//声明一个全局标量,两个线程访问这个变量
time_t end_time;//线程共执行的时间
sem_t sem;//信号量

void pthread1(void *arg) {

    int i;
    while(time(NULL) < end_time) {
        sem_wait(&sem);//信号量减1,P操作。
        for(i=0;i<2;i++) {
            sleep(1);
            lock_var++;
            printf("lock_var=%d\n",lock_var);
        }
        printf("pthread1:lock_var=%d\n",lock_var);
        sem_post(&sem);//信号量加1,V操作。
        sleep(1);
    }

}

void pthread2(void *arg) {

    while(time(NULL) < end_time) {
        sem_wait(&sem);//信号量减1,P操作。
        printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
        sem_post(&sem);//信号量加1,V操作。
        sleep(3);
    }

}

void main(int argc,char *argv[]) {
    pthread_t id1,id2;

    int ret;
    end_time = time(NULL) + 30;//设置结束时间为当前时间的30秒之后
    ret = sem_init(&sem,0,1);//初始化信号量为1

    if(ret != 0) {
        perror("sem_init");
    }

    ret = pthread_create(&id1,NULL,(void*)pthread1,NULL);//创建线程1
    if(ret != 0) {
        perror("pthread1 cread1");
    }

    ret = pthread_create(&id2,NULL,(void*)pthread2,NULL);//创建线程2
    if(ret != 0) {
        perror("pthread2 cread2");
    }

    pthread_join(id1,NULL);//等待线程1完成
    pthread_join(id2,NULL);//等待线程2完成
    exit(0);

}

执行结果:

使用信号量控制线程互斥和同步。_第3张图片

因为先调用线程一,所以线程一先执行,线程二堵塞。线程二执行的时候线程一又堵塞了。这就完成了互斥。

下面是使用信号量来完成信号量的同步:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int lock_var;//声明一个全局标量,两个线程访问这个变量
time_t end_time;//线程共执行的时间
sem_t sem1,sem2;//信号量

void pthread1(void *arg) {

    int i;
    while(time(NULL) < end_time) {
        sem_wait(&sem2);//信号量减1,P操作信号量2。
        for(i=0;i<2;i++) {
            sleep(1);
            lock_var++;
            printf("lock_var=%d\n",lock_var);
        }
        printf("pthread1:lock_var=%d\n",lock_var);
        sem_post(&sem1);//信号量加1,V操作信号量1。
        sleep(1);
    }

}

void pthread2(void *arg) {

    while(time(NULL) < end_time) {
        sem_wait(&sem1);//信号量减1,P操作。
        printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
        sem_post(&sem2);//信号量加1,V操作。
        sleep(3);
    }

}

void main(int argc,char *argv[]) {
    pthread_t id1,id2;

    int ret;
    end_time = time(NULL) + 30;//设置结束时间为当前时间的30秒之后
    ret = sem_init(&sem1,0,1);//初始化信号量为1
    if(ret != 0) {
        perror("sem1_init");
    }

    ret = sem_init(&sem2,0,0);//初始化信号量为1
    if(ret != 0) {
        perror("sem2_init");
    }

    ret = pthread_create(&id1,NULL,(void*)pthread1,NULL);//创建线程1
    if(ret != 0) {
        perror("pthread1 cread1");
    }

    ret = pthread_create(&id2,NULL,(void*)pthread2,NULL);//创建线程2
    if(ret != 0) {
        perror("pthread2 cread2");
    }

    pthread_join(id1,NULL);//等待线程1完成
    pthread_join(id2,NULL);//等待线程2完成
    exit(0);

}

执行结果:

使用信号量控制线程互斥和同步。_第4张图片

可以看到,即是代码先调用的线程一,但是因为此时sem2的值为0,所以堵塞线程一。而因为sem1的值为1,所以先执行线程二了。并且线程二执行完成将sem2加一成了0,此时线程一再次执行。而此时如果线程二再执行的话sem1就是-1了,也被堵塞,直到线程一将sem1加一才会被唤醒。这样一来就会线程二执行完成之后线程一才会执行,线程一执行完线程二继续。轮番的执行。这就是同步操作。如果想要线程一先执行,那么修改sem2为1,sem1为0即可。


如果在linux环境中编译代码出现以下错误:

使用信号量控制线程互斥和同步。_第5张图片

可以添加参数- pthread即可:

gcc -pthread sem_syn.c -o sem_syn

你可能感兴趣的:(Linux,C/C++,线程,C语言,信号量,同步,互斥)