
在进程之间进行通信时,已经提到了信号量和同步. 在这里,它们的含义相同,只是同步的对象不同. 但是,下面描述的信号量接口用于线程的信号量. 注意不要与进程间通信的信号灯混淆.
一个,什么是信号量
线程的信号量与进程间通信中使用的信号量相同. 它是一个可以增加或减少的特殊变量,但是保证对其的键访问是原子操作. 如果程序中有多个线程试图更改信号量的值,则系统将确保所有操作将按顺序执行.
只有两个值0和1的信号量称为二进制信号量,将在此处突出显示. 信号量通常用于保护一段代码,因此它一次只能由一个执行线程运行. 我们可以使用二进制信号量来做到这一点.
第二,信号量的界面和使用
所有信号量功能均以sem_开头. 线程中使用了四个基本的信号量函数. 它们都在头文件semaphore.h中声明.
1,sem_init函数

此函数用于创建信号量,其原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
此函数初始化sem指向的信号对象,设置其共享选项,并为其提供初始整数值. pshared控制信号量的类型. 如果其值为0,则表示该信号量是当前进程的局部信号量,否则该信号量可以在多个进程之间共享,并且该值是sem的初始值. 呼叫成功时返回0,失败则返回-1.
2,sem_wait函数
此功能用于通过原子操作减少信号量的值. 原子操作是,如果两个线程试图同时向一个信号量加减1,它们将不会互相干扰. 其原型如下:
int sem_wait(sem_t *sem);
sem指向的对象是由sem_init调用初始化的信号量. 呼叫成功时返回0,失败则返回-1.

3,sem_post函数
此功能用于通过原子操作来增加信号量的值. 其原型如下:
int sem_post(sem_t *sem);
像sem_wait一样,sem指向的对象是sem_init调用初始化的信号量. 呼叫成功时返回0,失败则返回-1.
4,sem_destroy函数
此功能用于清除使用的信号量. 其原型如下:
int sem_destroy(sem_t *sem);

成功时返回0,失败时返回-1.
三,使用信号量同步线程
以下使用一个简单的多线程程序来说明如何使用信号量进行线程同步. 在主线程中,我们创建一个子线程,并将数组msg作为参数传递给该子线程. 然后主线程等待直到有文本输入为止,然后调用sem_post来增加信号量的值,这将立即使sem_wait中的子线程等待返回并开始执行. 线程函数将字符串的小写字母更改为大写并计算输入的字符数后,它将再次调用sem_wait,并再次被阻塞,直到主线程再次调用sem_post来增加信号量的值.
#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)
{
fgets(msg, MSG_SIZE, stdin);
//把信号量加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';
}
}
printf("You input %d characters\n", i-1);
printf("To Uppercase: %s\n", ptr);
//把信号量减1
sem_wait(&sem);
}
//退出线程
pthread_exit(NULL);
}
结果如下:
从运行结果来看,该程序确实正在同时运行两个线程,一个控件输入,另一个控件处理统计信息和输出.
第四,分析该信号量同步程序的缺陷

但是此程序有一个小问题linux多线程服务器设计,即该程序依赖于接收足够长的文本输入,以便子线程有足够的时间在主线程准备好给它更多的单词进行处理和计数之前. 工作区中的字符数已经过处理和计数. 因此,当我们连续快速地给它提供两组不同的单词进行计数时,子线程没有足够的时间执行,但是信号量已经增加了不止一次,因此字符计数线程(子线程)将反复处理并计数字符数,并减小信号量的值,直到再次变为0.
为了更清楚地说明上述情况,请按如下所示修改主线程的while循环中的代码:
printf("Input some text. Enter 'end'to finish...\n");
while(strcmp("end\n", msg) != 0)
{
if(strncmp("TEST", msg, 4) == 0)
{
strcpy(msg, "copy_data\n");
sem_post(&sem);
}
fgets(msg, MSG_SIZE, stdin);
//把信号量加1
sem_post(&sem);
}
重新编译程序,结果如下:
当我们进入TEST时,主线程向子线程提供了两个输入,一个是从键盘输入的,另一个是从主线程到msg的输入linux多线程服务器设计,然后从操作结果中我们可以看到在操作中是一个例外. 没有用于从键盘输入TEST字符串的处理和统计信息,但是重复的数据被处理了两次. 原因如上所述.
五种解决此缺陷的方法
有两种解决方案,一种是添加另一个信号量,然后让主线程等待子线程处理统计信息完成后再继续执行;另一种方法是使用互斥锁.
下面给出了通过添加信号量来解决此问题的代码. 源文件名为semthread2.c,源代码如下:
#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;//信号量
sem_t sem_add;//增加的信号量
#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);
}
//初始化信号量,初始值为1
res = sem_init(&sem_add, 0, 1);
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");
sem_wait(&sem_add);
while(strcmp("end\n", msg) != 0)
{
if(strncmp("TEST", msg, 4) == 0)
{
strcpy(msg, "copy_data\n");
sem_post(&sem);
//把sem_add的值减1,即等待子线程处理完成
sem_wait(&sem_add);
}
fgets(msg, MSG_SIZE, stdin);
//把信号量加1
sem_post(&sem);
//把sem_add的值减1,即等待子线程处理完成
sem_wait(&sem_add);
}
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);
sem_destroy(&sem_add);
exit(EXIT_SUCCESS);
}
void* thread_func(void *msg)
{
char *ptr = msg;
//把信号量减1
sem_wait(&sem);
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';
}
}
printf("You input %d characters\n", i-1);
printf("To Uppercase: %s\n", ptr);
//把信号量加1,表明子线程处理完成
sem_post(&sem_add);
//把信号量减1
sem_wait(&sem);
}
sem_post(&sem_add);
//退出线程
pthread_exit(NULL);
操作结果如下:
分析: 在这里,我们使用一个额外的信号量sem_add并将其初始值分配给1. 主线程正在使用sem_wait等待子线程完全处理. 因为它的初始值为1,所以主线程是第一个对sem_wait的调用始终立即返回,而第二个调用则需要等待子线程完成处理. 在子线程中,如果处理完成,将立即使用sem_post来增加信号量的值,以便主线程中的sem_wait立即返回并立即执行下面的代码. 从操作结果来看,操作终于正常了. 请注意,程函数中,信号量sem和sem_add使用sem_wait和sem_post函数的顺序,并且它们的顺序不能无序,否则当输入end时end可能无法正常运行,并且子线程无法正常退出,导致程序无法退出.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-241589-1.html
打倒一切来犯之敌
敢独立就可以挂上五星红旗