网络编程(39)—— 使用信号量semaphore进行多线程同步

一、Semaphore相关函数

      之前介绍过了多线程中利用互斥mutex控制多线程中对临界区的访问方法,本文主要介绍下利用信号量semaphore控制线程对临界区的访问。
首先,我们先看一下semaphore相关的函数:

1.1 头文件

#include<semaphore.h>

        与之前互斥mutex所引用的头文件pthread.h不同,semaphore的相关函数在semaphore.h头文件中声明(在/usr/include/目录中)。

1.2 初始化函数

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

        sem是我们要创建的信号量的地址。sem_t的本质是一个联合体union,其定义在/usr/bits/semaphore.h文件中,请注意这个不是我们引入的semaphore.h文件。这个头文件只包含了对sem_t的定义,没有其他内容。
typedef union
{
char __size[__SIZEOF_SEM_T];
long int __align;
} sem_t;

        pshraed是semaphore需要跨越使用的进程数,设置0时只在一个进程中使用,设置1时可以在两个进程中使用,以此类推。现在明白为什么定义semaphore相关的函数需要在另用一个semaphore.h文件了吧,因为信号量是可以在多进程中使用的,而mutex只能在单进程中的多线程中使用。
        value是设置信号量设置的初始值,也是信号量的最大值。如果把临界区的锁定比喻成一把锁,那么信号量就是一把钥匙,而这里的value值相当于钥匙的数量。当一个线程需要访问一个临界区的时候,需要拿一把钥匙,那么信号量的值就会减1。当信号量的值减到0时其他线程便无法访问临界区了,直到已经访问过的线程交出钥匙。当value值设置成1时,显然信号量的作用就类似于mutex了。

1.3 销毁函数

int sem_destroy(sem_t *sem);

        销毁函数比较简单,sem就是要销毁的信号量的地址值。

1.4 等待信号量函数

int sem_wait(sem_t *sem);

        等待函数就是去打开锁的操作,类似于mutex的pthread_mutex_lock()函数,在进入临界区前调用。如果钥匙数为0(semaphore的值为0),它将会阻塞;如果钥匙数不为0(semaphore的值不为0),调用它后信号量的值将会减1.

1.5 释放信号量函数

int sem_post(sem_t* sem);

        释放信号量的函数类似mutex中的pthread_mutex_unlock()函数,在离开临界区后调用。每调用一次信号量的值就会加1.

二、semaphore的使用举例


2.1 利用semaphore控制多线程中临界区的访问

        第一个例子我们要把之前利用mutex控制的多线程例子改成用semaphore进行控制:

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
int num=0;
sem_t sem;
void* pthread_main1()
{
	int i=0;
	sem_wait(&sem);
	for(i=0;i<1000000000;i++)
	{
		--num;
	}
	sem_post(&sem);
}
void* pthread_main2()
{
	int i=0;
	sem_wait(&sem);	
	for(i=0;i<1000000000;i++)
	{
		++num;
	}
	sem_post(&sem);	
}

int main()
{
	int i=0;
	sem_init(&sem,0,1);
	pthread_t pid[2];
	pthread_create((void*)&(pid[0]),NULL,(void*)pthread_main1,NULL);
	pthread_create((void*)&(pid[1]),NULL,(void*)pthread_main2,NULL);
	for(i=0;i<2;i++)
	{
		pthread_join(pid[i],NULL);
	}
	printf("%d\n",num);
	sem_destroy(&sem);
	return 0;
}

第5行,声明了一个sem_t类型的全局变量sem。
第30行,对sem进行初始化,因为程序在单进程中运行,所以sem_init的第二个参数为0;第三个参数设置成1,这样就类似于mutex的效果,通过调用sem_wait和sem_post,信号量的值始终在1和0之间进行变动。
第9行和第19行利用sem_wait()等待信号量的值大于0,由于信号量的值在0和1之间变动,这两行始终只有一行代码会被执行,另一行代码进行阻塞。
第14行和第24行利用sem_post()函数释放信号量。
第39行,利用sem_wait()函数销毁信号量。
上述示例是演示利用semaphore替代mutex进行临界区访问控制的方法,最终也会达到了mutex一样的控制效果,结果如下:
[Hyman@Hyman-PC semphare]$ gcc sem.c -lpthread
[Hyman@Hyman-PC semphare]$ ./a.out 
0

2.2 利用semaphore控制socket服务端的最大客户端连接数

        接下来,我们编写一个使用信号量控制客户端连接数的多线程服务端,与之前的服务端不同,我们使用定义一个值为5的信号量。
sem_t sem;
semaphore_init(&sem,0,5);

        因为无需要多进程,所以初始化函数中第二个参数设置成0,最后一个参数设置成5,用来限制连接的客户端数。先把源代码放上来,后面再进行分析:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define BUF_SIZE 1024

sem_t sem;
void* pthread_main(void* param)
{
    int str_len;
    char buf[BUF_SIZE];
    int clnt_sock=*((int*)param);
    while(1)
    {
        str_len=read(clnt_sock,buf,BUF_SIZE);
        if(str_len<=0)
            break;
        write(clnt_sock,buf,str_len);
    }
    sem_post(&sem);
    close(clnt_sock);
}

int main(int argc,char* argv[])
{
    int serv_sock,clnt_sock;
    struct sockaddr_in serv_addr,clnt_addr;
    int clnt_addr_sz;
    pthread_t thread_id;

    if(argc!=2)
    {
        printf("Usage %s <port>\n",argv[0]);
        exit(1);
    }
    serv_sock=socket(AF_INET,SOCK_STREAM,0);

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    listen(serv_sock,10);
    
    sem_init(&sem,0,5);
    while(1)
    {
        sem_wait(&sem);
        clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);
        pthread_create(&thread_id,NULL,(void*)pthread_main,&clnt_sock);
    }
    //pthread_join(thread_id,NULL);
    sem_destroy(&sem);
    return 0;
}

第10行,创建了一个信号量sem,因为在每个线程中都有使用,所以我们把它声明成了全局变量
第50行,调用sem_init()对sem进行初始化,由于未涉及到多进程,所以第二个参数是0;由于想限制最多接收5个客户端的连接,所以第三个参数设置为5.
第53行,调用sem_wait()等待信号量,如果信号量树大于0,则执行下面的代码;否则发生阻塞;
第23行,调用sem_post()释放信号量,只有在一个线程中客户端结束连接后才会释放一个信号量,这样才能有新的客户端连接。
        运行时,当运行到第5个客户端时,一直输入正常,如下所示:
[Hyman@Hyman-PC semphare]$ ./clnt 127.0.0.1 9991
5
the message from server:5
        当运行第6个客户端时,服务端已经开始阻塞,客户端发给服务端的数据没得到回应:
[Hyman@Hyman-PC semphare]$ ./clnt 127.0.0.1 9991
6

        以上是对单进程中semaphore的使用一个简单的介绍,下一文将会介绍semaphore在多进程中的使用方法。


Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
Git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL39



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值