这里主要讲POSIX信号量。
信号量是Linux系统为应用层提供的进程/线程间同步的一种机制。
信号量分两种:无名信号量(unnamed semaphore)和有名信号量(named semaphore)。
两者区别:
区别 | 无名信号量 | 有名信号量 |
---|---|---|
创建方式 | int sem_init(sem_t *sem, int pshared, unsigned int value) | sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value); |
销毁方式 | int sem_destroy(sem_t *sem); | int sem_unlink(const char *name); |
存储方式 | 存放在内存中 | 存放在虚拟文件系统 |
相同点 | 无名信号量 | 有名信号量 |
---|---|---|
获取信号量(P操作) | int sem_wait(sem_t *sem); | int sem_wait(sem_t *sem); |
释放信号量(V操作) | int sem_post(sem_t *sem); | int sem_post(sem_t *sem); |
获取信号量有三种接口,不管有名信号量还是无名信号量均可通用:
接口 | 说明 |
---|---|
int sem_wait(sem_t *sem); | 如果信号量值为0,阻塞式等待另一端释放信号量 |
int sem_trywait(sem_t *sem); | 尝试获取信号量,如果当前信号量为0,则立即返回,errno置为EAGAIN |
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); | 设置等待超时abs_timeout,时间一到如果信号量依旧为0,则返回,errno置为ETIMEDOUT |
1. 无名信号量
无名信号量保存位置:
1.在线程间的“共享内存”区域(比如信号量句柄是全局变量);
2.进程间的共享内存中(比如信号量句柄为共享内存)。
如果是1的话则可用于线程间通信,2可用于进程间通信。
小提示:Linux 2.6版本之前的内核仅支持unnamed semaphore,线程间通信的信号量。
下面代码演示线程间通信:unnamed_thread_sema.c
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define INDEFINITE (0xFFFFFFFFu)
#define THREAD_SIZE (16*1024)
typedef sem_t* SEMA_HANDLE;
SEMA_HANDLE ThSemap=NULL;
int bIsSemaInit=0;
pthread_t ProduceThread;
pthread_t ConsumeThread;
int commodity=0;
int SemaphoreInit(void)
{
if(bIsSemaInit)
{
return 0;
}
ThSemap=(SEMA_HANDLE)malloc(sizeof(sem_t));
if(!ThSemap)
{
printf("Semaphore malloc error!\n");
return -1;
}
int rc=sem_init(ThSemap, 0, 1);
if(rc!=0)
{
perror("sem_init");
if(ThSemap)
{
free(ThSemap);
}
return -1;
}
bIsSemaInit=1;
return 0;
}
void SemaphoreDeInit(void)
{
if(ThSemap)
{
sem_destroy(ThSemap);
free(ThSemap);
ThSemap=NULL;
}
bIsSemaInit=0;
}
int SemaphoreWait(SEMA_HANDLE pSema, unsigned int timeout)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
struct timespec stTimeout;
memset(&stTimeout, 0x00, sizeof(struct timespec));
stTimeout.tv_sec=(long)timeout;
int rc = sem_timedwait(pSema, &stTimeout);
if(rc != 0)
{
if(errno==ETIMEDOUT)
{
printf("sem_timedwait timeout.\n");
}
return -1;
}
return 0;
}
int SemaphoreRelease(SEMA_HANDLE pSema)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
int rc=sem_post(pSema);
if(rc!=0)
{
printf("sem_post error.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
return 0;
}
void *produce_thread(void *parg)
{
while(1)
{
sleep(1);
SemaphoreWait(ThSemap, INDEFINITE);
commodity++;
printf("Produce commodity:%d\n", commodity);
SemaphoreRelease(ThSemap);
}
}
void *consume_thread(void *parg)
{
while(1)
{
sleep(1);
SemaphoreWait(ThSemap, INDEFINITE);
commodity--;
printf("Consume commodity:%d\n", commodity);
SemaphoreRelease(ThSemap);
}
}
//封装创建线程函数的原因:正常在项目中肯定涉及设置线程分离、线程栈大小等等,所以干脆重新封装一个
int CreateThread(pthread_t *tid, void *(*routine)(void *), unsigned int stacksize)
{
pthread_attr_t stThreadAttr;
memset(&stThreadAttr, 0x00, sizeof(pthread_attr_t));
int rc = pthread_attr_init(&stThreadAttr);//获取线程属性,下面有用
if(rc != 0)
{
printf("pthread_attr_init error.");
return -1;
}
rc = pthread_create(tid, &stThreadAttr, routine, NULL);//创建线程
if(rc != 0)
{
printf("pthread_create error.\n");
return -1;
}
//设置线程分离:目的防止进程意外退出时,线程的相关资源由内核回收
pthread_attr_setdetachstate(&stThreadAttr, PTHREAD_CREATE_DETACHED);
if(stacksize > 0)
{
//创建线程时,默认线程栈大小是16KB,如果需要设置栈大小,设置值必须要大于16KB才行,否则会设置失败
rc = pthread_attr_setstacksize(&stThreadAttr, stacksize);
if(rc != 0)
{
printf("pthread_attr_setstacksize error.\n");
pthread_attr_destroy(&stThreadAttr);
return -1;
}
}
//init完线程属性后,必须调用destroy销毁
pthread_attr_destroy(&stThreadAttr);
return 0;
}
int main(void)
{
int rc=SemaphoreInit();
if(rc!=0)
{
return -1;
}
rc = CreateThread(&ConsumeThread, consume_thread, THREAD_SIZE);
if(rc != 0)
{
SemaphoreDeInit();
return -1;
}
rc = CreateThread(&ProduceThread, produce_thread, THREAD_SIZE);
if(rc != 0)
{
SemaphoreDeInit();
return -1;
}
while(1);
return 0;
}
运行结果:
用于进程间通信的无名信号量,信号量句柄的存储位置为共享内存,共享内存有下面两种方式:
System V | POSIX | |
---|---|---|
创建 | int shmget(key_t key, size_t size, int shmflg); | int shm_open(const char *name, int oflag, mode_t mode); |
销毁 | int shmctl(int shmid, int cmd, struct shmid_ds *buf); | int shm_unlink(const char *name); |
本文主要主要介绍无名信号量,共享内存相关就不展开了。需要用到下面几个接口:
接口 | 说明 |
---|---|
int shm_open(const char *name, int oflag, mode_t mode); | 根据name创建一块共享内存,创建成功后返回一个文件描述符,name位于/dev/shm/name |
int shm_unlink(const char *name); | 删除name指定的路径,下次使用需要重新创建 |
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); | 将fd对应的文件,按指定大小映射到内存中,以进行读写操作 |
int munmap(void *addr, size_t length); | 解除内存映射,addr为mmap的返回值 |
int ftruncate(int fd, off_t length); | 重置文件大小:mmap内存映射成功后,name对应的文件大小为0,需要重置为申请的大小 |
OK,下面上代码
生产者 sema_produce.c
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define INDEFINITE (0xFFFFFFFFu)
#define SHARED_MEM_PATH "/proc_sema_mem"
typedef struct stShareMemSema{
sem_t stSemaHandle;
int commodity;
int bIsFinished;
}ShareMemSema_t;
ShareMemSema_t *pSemaMem=NULL;
int bIsSemaInit=0;
int gShmfd=-1;
int shared_memory_init(void)
{
int shmfd = shm_open(SHARED_MEM_PATH, O_RDWR|O_CREAT, 0666);
if(shmfd<0)
{
if(errno!=EEXIST)
{
printf("shm_open failed\n");
return -1;
}
}
pSemaMem=(ShareMemSema_t *)mmap(NULL, sizeof(ShareMemSema_t), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
if(!pSemaMem || pSemaMem==MAP_FAILED)
{
printf("%s\n", strerror(errno));
shm_unlink(SHARED_MEM_PATH);
return -1;
}
struct stat fst;
memset(&fst, 0x00, sizeof(struct stat));
fstat(shmfd, &fst);
if(fst.st_size==0)
{
ftruncate(shmfd, sizeof(ShareMemSema_t));
}
return 0;
}
void shared_memory_destroy(void)
{
if(pSemaMem)
{
munmap(pSemaMem, sizeof(ShareMemSema_t));
}
close(gShmfd);
printf("shm_unlink %s\n", SHARED_MEM_PATH);
shm_unlink(SHARED_MEM_PATH);
}
int SemaphoreInit(void)
{
if(bIsSemaInit)
{
return 0;
}
if(shared_memory_init() != 0)
{
printf("shared_memory_init error.\n");
return -1;
}
int rc=sem_init(&pSemaMem->stSemaHandle, 1, 1);
if(rc!=0)
{
perror("sem_init");
shared_memory_destroy();
return -1;
}
bIsSemaInit=1;
return 0;
}
void SemaphoreDeInit(void)
{
if(pSemaMem)
{
sem_destroy(&pSemaMem->stSemaHandle);
}
shared_memory_destroy();
bIsSemaInit=0;
}
int SemaphoreWait(sem_t *pSema, unsigned int timeout)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
struct timespec stTimeout;
memset(&stTimeout, 0x00, sizeof(struct timespec));
stTimeout.tv_sec=(long)timeout;
int rc = sem_timedwait(pSema, &stTimeout);
if(rc != 0)
{
if(errno==ETIMEDOUT)
{
printf("sem_timedwait timeout.\n");
}
return -1;
}
return 0;
}
int SemaphoreRelease(sem_t *pSema)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
int rc=sem_post(pSema);
if(rc!=0)
{
printf("sem_post error.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
return 0;
}
int main(void)
{
int rc=SemaphoreInit();
if(rc!=0)
{
return -1;
}
while(1)
{
SemaphoreWait(&pSemaMem->stSemaHandle, INDEFINITE);
printf("Produce commodity:%d\n", pSemaMem->commodity);
if(!pSemaMem->bIsFinished)
{
pSemaMem->commodity++;
}
else
{
printf("Produce finished:%d\n", pSemaMem->commodity);
SemaphoreRelease(&pSemaMem->stSemaHandle);
break;
}
SemaphoreRelease(&pSemaMem->stSemaHandle);
sleep(1);
}
SemaphoreDeInit();
return 0;
}
消费者 sema_consume.c
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#define INDEFINITE (0xFFFFFFFFu)
#define SHARED_MEM_PATH "/proc_sema_mem"
typedef struct stShareMemSema{
sem_t stSemaHandle;
int commodity;
int bIsFinished;
}ShareMemSema_t;
ShareMemSema_t *pSemaMem=NULL;
int bIsSemaInit=0;
int gShmfd=-1;
int shared_memory_init(void)
{
gShmfd = shm_open(SHARED_MEM_PATH, O_RDWR|O_CREAT, 0666);
if(gShmfd<0)
{
if(errno!=EEXIST)
{
printf("shm_open failed\n");
}
return -1;
}
pSemaMem=(ShareMemSema_t *)mmap(NULL, sizeof(ShareMemSema_t), PROT_READ|PROT_WRITE, MAP_SHARED, gShmfd, 0);
if(pSemaMem==MAP_FAILED)
{
printf("mmap error.\n");
shm_unlink(SHARED_MEM_PATH);
return -1;
}
return 0;
}
void shared_memory_destroy(void)
{
if(pSemaMem)
{
munmap(pSemaMem, sizeof(ShareMemSema_t));
}
close(gShmfd);
#if 0
shm_unlink(SHARED_MEM_PATH);
#endif
}
int SemaphoreInit(void)
{
if(bIsSemaInit)
{
return 0;
}
if(shared_memory_init() != 0)
{
printf("shared_memory_init error.\n");
return -1;
}
int rc=sem_init(&pSemaMem->stSemaHandle, 0, 1);
if(rc!=0)
{
perror("sem_init");
shared_memory_destroy();
return -1;
}
bIsSemaInit=1;
return 0;
}
void SemaphoreDeInit(void)
{
if(pSemaMem)
{
sem_destroy(&pSemaMem->stSemaHandle);
}
shared_memory_destroy();
bIsSemaInit=0;
}
int SemaphoreWait(sem_t *pSema, unsigned int timeout)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
struct timespec stTimeout;
memset(&stTimeout, 0x00, sizeof(struct timespec));
stTimeout.tv_sec=(long)timeout;
int rc = sem_timedwait(pSema, &stTimeout);
if(rc != 0)
{
if(errno==ETIMEDOUT)
{
printf("sem_timedwait timeout.\n");
}
return -1;
}
return 0;
}
int SemaphoreRelease(sem_t *pSema)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
int rc=sem_post(pSema);
if(rc!=0)
{
printf("sem_post error.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
return 0;
}
int main(void)
{
int rc=SemaphoreInit();
if(rc!=0)
{
return -1;
}
while(1)
{
SemaphoreWait(&pSemaMem->stSemaHandle, INDEFINITE);
printf("Consume commodity:%d\n", pSemaMem->commodity);
if(pSemaMem->commodity==20)
{
pSemaMem->bIsFinished=1;
SemaphoreRelease(&pSemaMem->stSemaHandle);
sleep(1);
break;
}
SemaphoreRelease(&pSemaMem->stSemaHandle);
sleep(1);
}
SemaphoreDeInit();
return 0;
}
简易Makefile
all:sema_produce sema_consume
.PHONY=all
sema_produce:sema_produce.o
cc -g -o sema_produce sema_produce.o -pthread -lrt
sema_consume:sema_consume.o
cc -g -o sema_consume sema_consume.o -pthread -lrt
.PHONY=clean
clean:
rm -rf sema_produce sema_consume *.o
运行效果:
2. 有名信号量
sem_t *sem_open(const char *name, int oflag);
创建一个新的或打开一个已经存在的以name为路径的POSIX信号量。创建的信号量保存在虚拟文件系统中,一般保存在/dev/shm/目录下,名字通常为sem.name。‘name’的格式为“/name”,长度必须不超过251个字节(肯定足够用了,一般用不了这么多)。
下面借助共享内存来演示用于进程间同步的有名信号量:
name_sema_produce.c
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define INDEFINITE (0xFFFFFFFFu)
#define NAMED_SEMA_PATH "/named_sema"
#define SHARE_MEM_PATH "/shared_memory"
typedef struct stShareMem{
int commodity;
int bIsFinished;
}ShareMem_t;
typedef sem_t* SEMA_HANDLE;
int gShmfd=-1;
int bIsSemaInit=0;
SEMA_HANDLE pSemaMem=NULL;
ShareMem_t *pShareMemory=NULL;
int shared_memory_init(void)
{
gShmfd = shm_open(SHARE_MEM_PATH, O_RDWR|O_CREAT, 0666);
if(gShmfd<0)
{
if(errno!=EEXIST)
{
printf("shm_open failed\n");
}
return -1;
}
pShareMemory=(ShareMem_t *)mmap(NULL, sizeof(ShareMem_t), PROT_READ|PROT_WRITE, MAP_SHARED, gShmfd, 0);
if(pShareMemory==MAP_FAILED)
{
printf("mmap error.\n");
shm_unlink(SHARE_MEM_PATH);
return -1;
}
ftruncate(gShmfd, sizeof(ShareMem_t));
return 0;
}
void shared_memory_destroy(void)
{
if(pShareMemory)
{
munmap(pShareMemory, sizeof(ShareMem_t));
}
close(gShmfd);
shm_unlink(SHARE_MEM_PATH);
}
int SemaphoreInit(void)
{
if(bIsSemaInit)
{
return 0;
}
pSemaMem = sem_open(NAMED_SEMA_PATH, O_RDWR|O_CREAT, 0666, 1);
if(pSemaMem == SEM_FAILED)
{
perror("sem_open");
printf("%s\n", strerror(errno));
return -1;
}
int rc=sem_init(pSemaMem, 1, 1);
if(rc!=0)
{
perror("sem_init");
sem_close(pSemaMem);
return -1;
}
bIsSemaInit=1;
return 0;
}
void SemaphoreDeInit(void)
{
if(pSemaMem)
{
sem_close(pSemaMem);
}
sem_unlink(NAMED_SEMA_PATH);
bIsSemaInit=0;
}
int SemaphoreWait(SEMA_HANDLE pSema, unsigned int timeout)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
struct timespec stTimeout;
memset(&stTimeout, 0x00, sizeof(struct timespec));
stTimeout.tv_sec=(long)timeout;
int rc = sem_timedwait(pSema, &stTimeout);
if(rc != 0)
{
if(errno==ETIMEDOUT)
{
printf("sem_timedwait timeout.\n");
}
return -1;
}
return 0;
}
int SemaphoreRelease(SEMA_HANDLE pSema)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
int rc=sem_post(pSema);
if(rc!=0)
{
printf("sem_post error.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
return 0;
}
int main(void)
{
int rc=SemaphoreInit();
if(rc!=0)
{
return -1;
}
rc = shared_memory_init();
if(rc != 0)
{
SemaphoreDeInit();
return -1;
}
while(1)
{
SemaphoreWait(pSemaMem, INDEFINITE);
printf("Produce commodity:%d\n", pShareMemory->commodity);
if(!pShareMemory->bIsFinished)
{
pShareMemory->commodity++;
}
else
{
printf("Produce finished:%d\n", pShareMemory->commodity);
SemaphoreRelease(pSemaMem);
break;
}
SemaphoreRelease(pSemaMem);
sleep(1);
}
SemaphoreDeInit();
shared_memory_destroy();
return 0;
}
name_sema_consume.c
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define INDEFINITE (0xFFFFFFFFu)
#define NAMED_SEMA_PATH "/named_sema"
#define SHARE_MEM_PATH "/shared_memory"
typedef struct stShareMem{
int commodity;
int bIsFinished;
}ShareMem_t;
typedef sem_t* SEMA_HANDLE;
int gShmfd=-1;
int bIsSemaInit=0;
SEMA_HANDLE pSemaMem=NULL;
ShareMem_t *pShareMemory=NULL;
int shared_memory_init(void)
{
gShmfd = shm_open(SHARE_MEM_PATH, O_RDWR, 0666);
if(gShmfd<0)
{
if(errno!=EEXIST)
{
printf("shm_open failed\n");
}
return -1;
}
pShareMemory=(ShareMem_t *)mmap(NULL, sizeof(ShareMem_t), PROT_READ|PROT_WRITE, MAP_SHARED, gShmfd, 0);
if(pShareMemory==MAP_FAILED)
{
printf("mmap error.\n");
return -1;
}
return 0;
}
void shared_memory_destroy(void)
{
if(pShareMemory)
{
munmap(pShareMemory, sizeof(ShareMem_t));
}
close(gShmfd);
}
int SemaphoreInit(void)
{
if(bIsSemaInit)
{
return 0;
}
pSemaMem = sem_open(NAMED_SEMA_PATH, O_RDWR|O_CREAT, 0666, 1);
if(pSemaMem == SEM_FAILED)
{
perror("sem_open");
printf("%s\n", strerror(errno));
return -1;
}
int rc=sem_init(pSemaMem, 1, 1);
if(rc!=0)
{
perror("sem_init");
sem_close(pSemaMem);
return -1;
}
bIsSemaInit=1;
return 0;
}
void SemaphoreDeInit(void)
{
if(pSemaMem)
{
sem_close(pSemaMem);
}
bIsSemaInit=0;
}
int SemaphoreWait(SEMA_HANDLE pSema, unsigned int timeout)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
struct timespec stTimeout;
memset(&stTimeout, 0x00, sizeof(struct timespec));
stTimeout.tv_sec=(long)timeout;
int rc = sem_timedwait(pSema, &stTimeout);
if(rc != 0)
{
if(errno==ETIMEDOUT)
{
printf("sem_timedwait timeout.\n");
}
return -1;
}
return 0;
}
int SemaphoreRelease(SEMA_HANDLE pSema)
{
if(!pSema)
{
printf("pSema is null.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
int rc=sem_post(pSema);
if(rc!=0)
{
printf("sem_post error.%s %d\n", __FUNCTION__, __LINE__);
return -1;
}
return 0;
}
int main(void)
{
int rc=SemaphoreInit();
if(rc!=0)
{
return -1;
}
rc = shared_memory_init();
if(rc != 0)
{
SemaphoreDeInit();
return -1;
}
while(1)
{
SemaphoreWait(pSemaMem, INDEFINITE);
printf("Consume commodity:%d\n", pShareMemory->commodity);
if(pShareMemory->commodity==20)
{
pShareMemory->bIsFinished=1;
printf("Consume finished:%d\n", pShareMemory->commodity);
SemaphoreRelease(pSemaMem);
break;
}
SemaphoreRelease(pSemaMem);
sleep(1);
}
SemaphoreDeInit();
shared_memory_destroy();
return 0;
}