linux信号量---互斥与同步

时间:2022-07-06 15:17:25

信号量

信号量也就是操作系统中所用到的 PV 原语,它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。在讲信号量之前先说明 PV原语的工作原理。
PV原语通过操作信号量来处理进程间的同步与互斥的问题。

  • P原语:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞。
  • V原语:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

更加详细的说明,参考:linux互斥锁和PV原语

linux信号量相关函数说明:

Linux 实现了 POSIX 的无名信号量,主要用于线程间的互斥同步。这里主要介绍几个常见函数。

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

返回值:若成功,返回0;若出错,返回-1
参数:

  • sem:信号量
  • pshared:为零,则不能在进程之间共享信号。不为零,则能够在进程之间共享信号。
  • value:信号量初始化值
#include <pthread.h>
int sem_wait(sem_t *sem)
int sem_trywait(sem_t *sem)
int sem_post(sem_t *sem)
int sem_getvalue(sem_t *sem)
int sem_destroy(sem_t *sem)

返回值:若成功,返回0;若出错,返回-1
参数:
sem:信号量
各个函数简要说明:

  • sem_init 用于创建一个信号量,并能初始化它的值。
  • sem_wait 和 sem_trywait 相当于 P 操作,它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait 将会阻塞进程,而 sem_trywait 则会立即返回。
  • sem_post 相当于 V 操作,它将信号量的值加一同时发出信号唤醒等待的进程。
  • sem_getvalue 获取信号量的值。
  • sem_destroy 删除信号量

接下来,我们用两个例子讲如何用信号量来实现线程互斥锁和线程之间的同步。

一、用信号量实现互斥锁

/* sem_mutex_1.c*/

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <semaphore.h>

sem_t sem;
static int count = 0 ;
#define FILENAME "sem_file_save"
/*线程一*/
void thread1(void * arg)
{
char write_buf[] = "1";
int fd = *(int *)arg;
printf("this is pthread1,fd = %d\n",fd);
int i = 0;
while(1)
{
/*信号量减一,P 操作*/
sem_wait(&sem);
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread1....and count = %d\n",count);
/*信号量加一,V 操作*/
sem_post(&sem);
sleep(2);
}
}
/*线程二*/
void thread2(void * arg)
{
char write_buf[] = "2";
int fd = *(int *)arg;
printf("this is pthread2 and fd = %d\n",fd);
int i = 0;
while(1)
{
/*信号量减一,P 操作*/
sem_wait(&sem);
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}

printf("This is a pthread2.... count = %d\n",count);
/*信号量加一,V 操作*/
sem_post(&sem);
sleep(2);
}
}

int main(void)
{
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
printf("open success!\n");
int i,ret;
/*初始化信号量为 1*/
ret=sem_init(&sem,0,1);
if(ret!=0)
{
perror("sem_init");
}

pthread_t id1,id2;

/*创建线程一*/
ret=pthread_create(&id1,NULL,(void *) thread1,&fd);
if(ret!=0){
perror("Create pthread error!\n");
}
/*创建线程二*/
ret=pthread_create(&id2,NULL,(void *) thread2,&fd);
if(ret!=0){
perror ("Create pthread error!\n");
}

/*等待线程结束*/
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}

实验结果:

ubuntu:~/test/pthread_test$ gcc sem_mutex_1.c -o sem_mutex_1 -lpthread
ubuntu:~/test/pthread_test$ ./sem_mutex_1
open success!
this is pthread2 and fd = 3
this is pthread1,fd = 3
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 15
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 20

从上面的例子中,我们首先需要把头文件semaphore.h 包含进来,然后在main函数中调用sem_init(&sem,0,1),初始化信号量为 1。接着在在线程中,每次想要向文件写入数据前,都要执行sem_wait(&sem),相当于信号量减一,P 操作,这时信号量为0,另外一个线程执行sem_wait(&sem)的时候,就会阻塞。等到调用sem_post(&sem)之后,信号量加一,阻塞才会解除,程序才会继续往下走,这样就保证了在同一时间段,只有一个线程能访问到文件。
虽然线程一和线程二访问文件sem_file_save的时候能做到互斥,但是线程一或者线程二哪个先执行,我们是无法预测的。如果想要控制谁先谁后,那就得运用到同步的知识了。接下来是通过两个信号量来实现两个线程间的同步。

二、用信号量实现同步

/* sync_sem_mutex.c*/

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <semaphore.h>

sem_t sem1,sem2;
static int count = 0 ;
#define FILENAME "sync_sem_file"
/*线程一*/
void thread1(void * arg)
{
char write_buf[] = "1";
int fd = *(int *)arg;
printf("this is pthread1,fd = %d\n",fd);
int i = 0;
while(1)
{
/*P 操作信号量 sem1*/
sem_wait(&sem1);
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread1....and count = %d\n",count);
/*V 操作信号量 sem2*/
sem_post(&sem2);
sleep(2);
}
}
/*线程二*/
void thread2(void * arg)
{
char write_buf[] = "2";
int fd = *(int *)arg;
printf("this is pthread2 and fd = %d\n",fd);
int i = 0;
while(1)
{
/*P 操作信号量 sem2*/
sem_wait(&sem2);
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}

printf("This is a pthread2.... count = %d\n",count);
/*V 操作信号量 sem1*/
sem_post(&sem1);
sleep(2);
}
}

int main(void)
{
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
printf("open success!\n");
int i,ret;
/*初始化两个信号量,一个信号量为 1,一个信号量为 0*/
ret=sem_init(&sem1,0,1);
ret=sem_init(&sem2,0,0);

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

pthread_t id1,id2;

/*创建线程一*/
ret=pthread_create(&id1,NULL,(void *) thread1,&fd);
if(ret!=0){
perror("Create pthread error!\n");
}
/*创建线程二*/
ret=pthread_create(&id2,NULL,(void *) thread2,&fd);
if(ret!=0){
perror ("Create pthread error!\n");
}

/*等待线程结束*/
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}

实验结果:

ubuntu:~/test/pthread_test$ gcc sync_sem_mutex.c -o sync_sem_mutex -lpthread
ubuntu:~/test/pthread_test$ ./sync_sem_mutex
open success!
this is pthread2 and fd = 3
this is pthread1,fd = 3
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 15
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 20

无论运行多少次,先执行都是线程一,然后再运行线程二。