上文中简单的说了一下互斥锁在多线程同步上的应用,本文来简单的说一说信号量在多线程同步之间的应用。首先来简单的说一说信号量在多线程之间同步的基本概念和操作,然后编写一个小例子对其进行测试。
一、信号量的基本概念和操作
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。这里先来简单复习一下PV 原子操作的工作原理。PV 原子操作是对整数计数器信号量sem 的操作。一次P 操作使sem 减一,而一次V 操作使sem 加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem 的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem 的值小于零时,该进程(或线程)就将阻塞直到信号量sem 的值大于等于0 为止。
Linux 实现了POSIX 的无名信号量,主要用于线程间的互斥与同步。这里主要介绍几个常见函数。
a、信号量初始化函数
int sem_init(sem_t *sem,int pshared,unsigned int value)
sem : 要初始化的信号量的标识
pshared : 决定信号量能在几个进程间共享,如果只在本进程使用设为0
value:信号量初始化值
返回值:0成功,其他错误
b、其他信号量常用函数
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) // 销毁一个信号量
sem : 要操作的信号量的标识
返回值:0成功,其他错误
二、测试
编写一个测试小例子,实现的功能:编写三个子线程,根据主线程传入的参数来决定三个子线程中同时有几个可以运行,完整的代码如下所示:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
/* 定义一个信号量的全局变量 */
static sem_t sem;
/*
* 子线程a的线程体
*/
static void *start_routine_a (void *arg)
{
int loop_cnt = (int)arg;
int i;
sem_wait(&sem); // 等待信号量
printf("thread A : start!\n");
for(i = 0; i < 5; i++)
{
printf("TECH-PRO thread A : %d\n", i);
sleep(1);
}
printf("thread A : stop!\n");
sem_post(&sem); // 发送信号量
pthread_exit(NULL); /* 线程执行完毕 */
}
/*
* 子线程b的线程体
*/
static void *start_routine_b (void *arg)
{
int loop_cnt = (int)arg;
int i;
sem_wait(&sem); // 等待信号量
printf("thread B : start!\n");
for(i = 0; i < 5; i++)
{
printf("TECH-PRO thread B : %d\n", i);
sleep(1);
}
printf("thread B : stop!\n");
sem_post(&sem); // 发送信号量
pthread_exit(NULL); /* 线程执行完毕 */
}
/*
* 子线程c的线程体
*/
static void *start_routine_c (void *arg)
{
int loop_cnt = (int)arg;
int i;
sem_wait(&sem); // 等待信号量
printf("thread C : start!\n");
for(i = 0; i < 5; i++)
{
printf("TECH-PRO thread C : %d\n", i);
sleep(1);
}
printf("thread C : stop!\n");
sem_post(&sem); // 发送信号量
pthread_exit(NULL); /* 线程执行完毕 */
}
/*
* 定义三个子线程根据传入的值来决定让三个线程中几个线程同时运行
* usage : pthread <1|2|3>
*/
int main(int argc, char *argv[])
{
int ret;
int value;
pthread_t pthread_a, pthread_b, pthread_c;
if(2 != argc)
{
printf("usage : %s <1|2|3>\n", argv[0]);
return -1;
}
/* 从传入的参数当中获取这个数值 */
value = strtoul(argv[1], NULL, 0);
if((value < 1) || (value > 3))
{
printf("the value is out of boundary!\n");
return -1;
}
/* 初始化信号量 */
ret = sem_init(&sem, 0, value);
if(0 != ret)
{
printf("sem_init error!\n");
return -1;
}
/* 开始 */
printf("main : start!\n");
/* 创建两个子线程 */
ret = pthread_create(&pthread_a, NULL, start_routine_a, (void *)5);
if(0 != ret)
{
printf("pthread_create for a is failed!\n");
return -1;
}
ret = pthread_create(&pthread_b, NULL, start_routine_b, (void *)5);
if(0 != ret)
{
printf("pthread_create for b is failed!\n");
return -1;
}
ret = pthread_create(&pthread_c, NULL, start_routine_c, (void *)5);
if(0 != ret)
{
printf("pthread_create for c is failed!\n");
return -1;
}
/* 等待进程结束 */
pthread_join(pthread_a, NULL);
pthread_join(pthread_b, NULL);
pthread_join(pthread_c, NULL);
/* 结束 */
printf("main : stop!\n");
sem_destroy(&sem); // 销毁信号量
return 0;
}
编译这个例程,编译和链接时要加入-lpthread,运行分为一个三种情况:
当同时只有一个线程运行时:
当同时可以有两个线程运行时:
当同时有三个线程运行时:
从上面结果可以看出,使用信号量可以决定同时有多少个线程可以对共享资源和空间进行访问。