经典同步问题实现(一)linux操作系统--使用POSIX信号量

一、头文件解析

1、#include <pthread.h>

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。

关于链接libpthread.a库:
一般情况下,我们在链接一个(文件名为libxxx.so或libxxx.a等的)库时,
会使用-lxxx的方式;在Linux中要用到多线程时,需要链接pthread库,
按照惯例,我们应该使用-lpthread的方式来进行链接;但是,
通过日常的观察,我发现很多开源代码都是使用了-pthread参数,
而非使用-lpthread,这是为什么呢?

一通Google后,总算找到一些线索:

为了可移植性:在Linux中,pthread是作为一个单独的库存在的(libpthread.so),
但是在其他Unix变种中却不一定,比如在FreeBSD中是没有单独的pthread库的,
因此在FreeBSD中不能使用-lpthread来链接pthread,
而使用-pthread则不会存在这个问题,
因为FreeBSD的编译器能正确将-pthread展开为该系统下的依赖参数。
同样道理,其他不同的变种也会有这样那样的区别,如果使用-lpthread,
则可能在移植到其他Unix变种中时会出现问题,为了保持较高的可移植性,
我们最好还是使用-pthread(尽管这种做法未被接纳成为C标准,但已基本是事实标准)。
添加额外的标志:在多数系统中,-pthread会被展开为“-D_REENTRANT -lpthread”,
即是除了链接pthread库外,还先定义了宏_REENTRANT。
定义这个宏的目的,是为了打开系统头文件中的各种多线程支持分支。
比如,我们常常使用的错误码标志errno,如果没有定义_REENTRANT,
则实现为一个全局变量;若是定义了_REENTRANT,
则会实现为每线程独有,从而避免线程竞争错误。

转自:关于-pthread和-lpthread的区别

一些pthread相关函数

#include <pthread.h>
​1)
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 
Compile and link with -pthread.
返回值:成功返回 0, 失败返回失败数字,并且第一个参数不会设置
pthtea_t 这个是进程号,
pthread_attr_t 线程属性结构体,这个参数通常为 NULL
start_routine  函数指针 传递进程函数名
arg 进程函数参数
2)
pthread_exit函数:退出当前线程
void pthread_exit(void *retval);
3)
pthread_join函数:以阻塞方式等待某一线程结束。
int pthread_join(pthread_t thread, void **retval);
参数:
    pthread_t 线程号 
    retval:  用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。
​4)
pthread_cancel函数:结束某个线程,只是请求取消运行
int pthread_cancel(pthread_t thread);
参数:
    进程号
返回值:
    成功返回 0  ,失败返回错误号
5)
pthread_self函数: 获取当前线程的TID
 pthread_t pthread_self(void);
 返回值:
    进程TID

 2、#include <semaphore.h>

发现了一篇非常优秀的文章,作为学习进程间通信-信号量semaphore的教程:semaphore

其中用户态进程使用的两种信号量的定义:

1)

SYSTEM V:System V 是 AT&T 的第一个商业UNIX版本(UNIX System III)的加强。传统上,System V 被看作是两种UNIX"风味"之一(另一个是 BSD)。然而,随着一些并不基于这两者代码的UNIX实现的出现,例如 Linux 和 QNX, 这一归纳不再准确,但不论如何,像POSIX这样的标准化努力一直在试图减少各种实现之间的不同。

2)

POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE 1003,而国际标准名称为ISO/IEC 9945。

POSIX 信号量与SYSTEM V信号量的比较 

1.对POSIX来说,信号量是个非负整数。常用于线程间同步。 
而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,
这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。

2.POSIX信号量的引用头文件是<semaphore.h>,而SYSTEM V信号量的引用头文件是<sys/sem.h>

3.从使用的角度,System V信号量是复杂的,而Posix信号量是简单。比如,POSIX信号量的创建和初始化或PV操作就很非常方便。

 

 posix信号量详解

无名信号量

无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。

无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

无名信号量相关函数

int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 1.pshared==0 用于同一多线程的同步;

  • 2.若pshared>0 用于多个相关进程间的同步(即由fork产生的)

  • int sem_getvalue(sem_t *sem, int *sval);

     

    1.取回信号量sem的当前值,把该值保存到sval中。 
    2.若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:

  • 1)返回0(被linux采用)

  • 2)返回阻塞在该信号量上的进程或线程数目

  • int sem_wait(sem_t *sem); // 这是一个阻塞的函数

    sem_wait(或sem_trywait)相当于P操作,即申请资源。

  • 测试所指定信号量的值,它的操作是原子的。

  • 若sem>0,那么它减1并立即返回。

  • 若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。

  • int sem_trywait(sem_t *sem); // 非阻塞的函数

    其他的行为和sem_wait一样,除了: 
    若sem==0,不是睡眠,而是返回一个错误EAGAIN。

  • int sem_post(sem_t *sem);

    sem_post相当于V操作,释放资源。

  • 把指定的信号量sem的值加1;

    呼醒正在等待该信号量的任意线程。 
    注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数

无名信号量在多线程间的同步

无名信号量的常见用法是将要保护的变量放在sem_wait和sem_post中间所形成的 
临界区内,这样该变量就会被保护起来。

类型定义:

typedef unsigned long int pthread_t;

//come from /usr/include/bits/pthreadtypes.h

用途:pthread_t用于声明线程ID。

sizeof(pthread_t) =8

pthread_t,在使用printf打印时,应转换为u类型。

 使用一个信号量sem_id实现两个线程间修改一个全局变量number的互斥。

practice1:

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int number;
sem_t sem_id;//声明一个无名信号量sem_id
void* thread_one_fun(void *arg)
{
	sem_wait(&sem_id);
	printf("thread_one have the semaphore\n");
	number++;
	printf("thread_one: number = %d \n", number);
	sem_post(&sem_id);
	return NULL;
} 
void* thread_two_fun(void *arg)
{
	sem_wait(&sem_id);
	printf("thread_two have the semaphore\n");
	number--;
	printf("thread_two: number = %d \n", number);
	sem_post(&sem_id);
	return NULL;
}
int main(int argc, char *argv[])
{
	number = 1;
	pthread_t id1, id2;
	sem_init(&sem_id, 0, 1);
	pthread_create(&id1, NULL, thread_one_fun, NULL);
	pthread_create(&id2, NULL, thread_two_fun, NULL);
	
	pthread_join(id1, NULL);//以阻塞方式等待这个进程的结束 
	pthread_join(id2, NULL);
	
	printf("main...\n");
	return 0; 
		
}
运行结果:
[651km@localhost ~]$ gcc semaphore1.c -pthread
[651km@localhost ~]$ ./a.out
thread_one have the semaphore
thread_one: number = 2 
thread_two have the semaphore
thread_two: number = 1 
main...
[651km@localhost ~]$ ./aout
bash: ./aout: No such file or directory
[651km@localhost ~]$ ./a.out
thread_one have the semaphore
thread_one: number = 2 
thread_two have the semaphore
thread_two: number = 1 
main...
[651km@localhost ~]$ ./a.out
thread_two have the semaphore
thread_two: number = 0 
thread_one have the semaphore
thread_one: number = 1 
main...
[651km@localhost ~]$ ./a.out
thread_one have the semaphore
thread_one: number = 2 
thread_two have the semaphore
thread_two: number = 1 
main...

practice2: 

  • 使用两个信号量实现两个线程间对number这个全局变量的修改

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <unistd.h>
 int number;
 sem_id1, sem_id2;
 
void* thread_one_fun(void *arg)
{
	sem_wait(&sem_id1);
	printf("thread_one have the semaphore\n");
	
	number++;
	
	priintf("number = %d\n", number);
	sem_post(&sem_id2);
	return NULL;
}

void* thread_two_fun(void* arg)
{
	sem_wait(&sem_id2);
	printf("thread_two have the semaphore\n");
	
	number--;
	printf("number = %d\n", number);
	sem_post(&sem_id1);
	return NULL;
}

int main(int argc, char* argc[])
{
	number = 1;
	pthread_t id1, id2;
	sem_init(&sem_id1, 0, 1);
	sem_init(&sem_id2, 0, 0);
	
	pthread_create(&id1, NULL, thread_one_fun, NULL);
	pthread_create(&id2, NULL, thread_two_fun, NULL);
	
	pthread_join(id1, NULL);
	pthread_join(id2, NULL);
	
	printf("main...\n");
	return EXIT_SUCCESS;	
}

 

运行结果:
[651km@localhost ~]$ gcc semaphore2.c -pthread
[651km@localhost ~]$ ./a.out
This is the first thread to execute
number = 2
This is the last thread to execute
number = 1
main...
[651km@localhost ~]$ 


practice3: 

在第一个程序的基础上实现使用一个信号量对两个线程间对number的修改实现互斥: 线程2在线程1之前执行

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int number;
sem_t sem_id;//声明一个无名信号量sem_id
void* thread_one_fun(void *arg)
{
	sem_wait(&sem_id);
	printf("thread_one have the semaphore\n");
	number++;
	printf("thread_one: number = %d \n", number);
	//sem_post(&sem_id);
	return NULL;
} 
void* thread_two_fun(void *arg)
{
	//sem_wait(&sem_id);
	printf("thread_two have the semaphore\n");
	number--;
	printf("thread_two: number = %d \n", number);
	sem_post(&sem_id);
	return NULL;
}
int main(int argc, char *argv[])
{
	number = 1;
	pthread_t id1, id2;
	sem_init(&sem_id, 0, 1);
	pthread_create(&id1, NULL, thread_one_fun, NULL);
	pthread_create(&id2, NULL, thread_two_fun, NULL);
	
	pthread_join(id1, NULL);//以阻塞方式等待这个进程的结束 
	pthread_join(id2, NULL);
	
	printf("main...\n");
	return 0; 
		
}
运行结果:
[651km@localhost ~]$ gcc semaphore3.c -pthread
[651km@localhost ~]$ ./a.out
thread_two have the semaphore
thread_two: number = 0 
thread_one have the semaphore
thread_one: number = 1 
main...
[651km@localhost ~]$ 

practice4: 

 相关头文件解析:

errno宏定义为一个int型态的左值, 包含任何函式使用errno功能所产生的上一个错误码。

一些表示错误码,定义为整数值的宏:

EDOM 源自于函式的参数超出范围,例如sqrt(-1)

ERANGE 源自于函式的结果超出范围,例如strtol("0xfffffffff",NULL,0)

EILSEQ 源自于不合法的字符顺序,例如wcstombs(str, L"\xffff", 2) 

#include <sys/stat.h>

文件状态,是unix/linux系统定义文件状态所在的伪标准头文件。

fcntl是计算机中的一种函数,通过fcntl可以改变已打开的文件性质。fcntl针对描述符提供控制。参数fd是被参数cmd操作的描述符。针对cmd的值,fcntl能够接受第三个参数int arg。

fcntl的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。

sys/mman.h - memory management declarations 内存管理声明 

 在用fork创建的父进程和子进程间使用mutex信号量:

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
	int fd, i;
	int nloop = 10, zero = 0;
	int *ptr;
	sem_t mutex;
	//open a file and map it into memory
	fd = open("log.txt", O_RDWR | O_CREAT, S_IRWXU);
	
	write(fd, &zero, sizeof(int));
	ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	close(fd);
	
	/*create, initial semaphore*/
	if(sem_init(&mutex, 1, 1) < 0)
	{
		perror("semaphore initialization");
		exit(0);
	}
	
	if(fork() == 0)
	{
		for(i=0; i<nloop; i++)
		{
			sem_wait(&mutex);
			printf("child: %d\n", (*ptr)++);
			sem_post(&mutex);
		}
		exit(0);
	}
	for(i=0; i<nloop; i++)
	{
		sem_wait(&mutex);
		printf("parent: %d\n", (*ptr)++);
		sem_post(&mutex);
	}
	exit(0);
}

 

运行结果:
[651km@localhost ~]$ gcc semaphore4.c -pthread
[651km@localhost ~]$ ./a.out
parent: 0
parent: 1
parent: 2
parent: 3
parent: 4
parent: 5
parent: 6
parent: 7
parent: 8
parent: 9
child: 10
child: 11
child: 12
child: 13
child: 14
child: 15
child: 16
child: 17
child: 18
child: 19
[651km@localhost ~]$ ./a.out
parent: 0
parent: 1
parent: 2
parent: 3
parent: 4
parent: 5
parent: 6
parent: 7
parent: 8
parent: 9
child: 10
child: 11
child: 12
child: 13
child: 14
child: 15
child: 16
child: 17
child: 18
child: 19
[651km@localhost ~]$ ./a.out
child: 0
parent: 1
parent: 2
parent: 3
parent: 4
parent: 6
child: 5
parent: 7
child: 8
parent: 9
child: 10
parent: 11
child: 12
parent: 13
child: 14
parent: 15
child: 16
child: 17
child: 18
child: 19
每次结果都是随机的。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值