目录
前言
在多线程编程中使用广泛的一种机制——信号量
一、信号量是什么?
1.量即数量,计数器
信号量本身代表一种资源,其本质是一个非负的整数计数器,被用来控制对公共资源的访问。换句话说,信号量的核心内容是信号量的值
2.工作原理
1.所有对共享资源操作的线程,在访问共享资源之前,都需要先操作信号量的值。操作信号量的值又可以称为PV操作,P操作为申请信号量,V操作为释放信号量
2.当申请信号量成功时,信号量的值减1,而释放信号量成功时,信号量的值加1。但是当信号量的值为0时,申请信号量时将会阻塞,其值不能减为负数
二、信号量的使用
1.引入头文件
#include <semaphore.h>
注:信号量作为一种同步互斥机制,若用于实现互斥时,多线程只需设置一个信号量。若用于实现同步时,则需要设置多个信号量,并通过设置不同的信号量的初始值来实现线程的执行顺序
2.声明信号量
类型sem_t
例:sem_t sem;
3.初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
参数sem表示信号量的标识符
pshared参数用来设置信号量的使用环境,其值为0,表示信号量用于同一个进程的多个线程之间使用;其值为非0,表示信号量用于进程间使用
value为重要的参数,表示信号量的初始值
成功返回0,失败返回-1
4.sem_post()释放信号量
当释放信号量成功时,信号量的值加1
int sem_post(sem_t *sem);
5.sem_wait()申请信号量
int sem_wait(sem_t *sem)
当申请信号量成功时,信号量的值减1,当信号量的值为0时,此操作将会阻塞,直到其他线程执行释放信号量
sem_trywait()函数:
int sem_trywait(sem_t *sem);
与sem_wait()函数类似,唯一的区别在于sem_trywait()函数不会阻塞,当信号量为0时,函数直接返回错误码EAGAIN
sem_timewait()函数:
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
多了参数abs_timeout,用来设置时间限制,如果在该时间内,信号量仍然不能申请,那么该函数不会一直阻塞,而是返回错误码ETIMEOUT
6.sem_getvalue()获得当前信号量的值
int sem_getvalue(sem_t *sem, int *sval);
sem_getvalue()函数用于获得当前信号量的值,并将值保存在参数sval中
7.sem_destroy()摧毁信号量
int sem_destroy(sem_t *sem);
三、代码示例
此程序实现从键盘输入字符串,并把小写字符转成大写,两线程工作,当键盘输入end,退出程序
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//线程函数
void *thread_func(void *msg);
sem_t sem;//信号量
#define MSG_SIZE 512
int main()
{
int res = -1;
pthread_t thread;
void *thread_result = NULL;
char msg[MSG_SIZE];
//初始化信号量,其初值为0
res = sem_init(&sem, 0, 0);
if(res == -1)
{
perror("semaphore intitialization failed\n");
exit(EXIT_FAILURE);
}
//创建线程,并把msg作为线程函数的参数
res = pthread_create(&thread, NULL, thread_func, msg);
if(res != 0)
{
perror("pthread_create failed\n");
exit(EXIT_FAILURE);
}
//输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
printf("Input some text. Enter 'end'to finish...\n");
while(strcmp("end\n", msg) != 0)//strcmp()相同返回0,输入字符串后外加一个enter键,相当于一个\n
{
fgets(msg, MSG_SIZE, stdin);//将键盘输入的字符送入meg中
//把信号量加1
sem_post(&sem);//发信号
}
printf("Waiting for thread to finish...\n");
//等待子线程结束
res = pthread_join(thread, &thread_result);
if(res != 0)
{
perror("pthread_join failed\n");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
//清理信号量
sem_destroy(&sem);
exit(EXIT_SUCCESS);
}
void* thread_func(void *msg)
{
//把信号量减1
sem_wait(&sem);
char *ptr = msg;
while(strcmp("end\n", msg) != 0)
{
int i = 0;
//把小写字母变成大写
for(; ptr[i] != '\0'; ++i)
{
if(ptr[i] >= 'a' && ptr[i] <= 'z')
{
ptr[i] -= 'a' - 'A';
//ptr[i]=ptr[i]-'a'+'A';
}
}
printf("You input %d characters\n", i-1);
printf("To Uppercase: %s\n", ptr);
//把信号量减1
sem_wait(&sem);
}
//退出线程
pthread_exit(NULL);
}
此处介绍两函数:
1.fgets()函数:
原型:
char *fgets(char *str, int n, FILE *stream)
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内
参数:
1.str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串
2.n -- 这是要读取的最大字符数
3.stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流
返回值:
成功:返回相同的 str 参数
失败:空指针
2.strcmp()函数:
原型:
int strcmp(const char *str1, const char *str2)
把 str1 所指向的字符串和 str2 所指向的字符串进行比较
参数
str1 -- 要进行比较的第一个字符串。
str2 -- 要进行比较的第二个字符串。
返回值:
若str1=str2,则返回零;
若str1<str2,则返回负数;
若str1>str2,则返回正数。