总结
- Postgresql使用匿名信号量完成进程间的一些同步操作。
- 匿名信号量由父进程创建在mmap的共享内存内,通过血缘关系继承给子进程,子进程从共享内存中获取信号量数据结构直接使用即可。
- Postgresql的信号量分配比较简单,每一个进程拥有一个自己的信号量。初始化后值为1,表示未锁定状态。
- 加锁后信号量=0。
- 解锁后信号量=1。
- Postgresql的信号量初始化使用的是POSIX接口(
SYSTEM V)中的匿名信号量(命名信号量)。
struct PGPROC
{
...
PGSemaphore sem;
... | -------- 是否共享,如果是需要在共享内存中获取newsem
}; | |
v v v----------- 初始值:1
if (sem_init(newsem, 1, 1) < 0)
...
POSIX相比SYSTEM V接口的优势:
- POSIX信号量接口与System V信号量接口相比要简单许多。
- 将一个 POSIX 未命名信号量与动态分配的内存对象关联起来更加简单:只需要将信号量嵌入到对象中即可。
- 在高度频繁地争夺信号量的场景中,POSIX 信号量的性能与 System V 信号量的性能是类似的。但在争夺信号量不那么频繁的场景中(即信号量的 值能够让操作正常执行而不会阻塞操作)POSIX 信号量的性能要比 System V 信号量好很多。POSIX 在这种场景中之所以能够做得更好是因为它们的实现方式只有在发生争夺的时候才需要执行系统调用,而 System V 信号量操作则不管是否发生争夺都 需要执行系统调用。
POSIX相对SYSTEM V接口的劣势:
- POSIX信号量的可移植性稍差。(Linux直到内核2.6才开始支持命名信号量)
- POSIX信号量不支持SystemV信号量中的撤销特性。(没啥用)
仿照Postgresql使用实例
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#define PG_CACHE_LINE_SIZE 128
#define PG_SEM_REF(x) (&(x)->sem_padded.pgsem)
typedef union SemTPadded
{
sem_t pgsem;
char pad[PG_CACHE_LINE_SIZE];
} SemTPadded;
typedef struct PGSemaphoreData
{
SemTPadded sem_padded;
} PGSemaphoreData;
typedef struct PGSemaphoreData *PGSemaphore;
static PGSemaphore sharedSemas;
int main(int argc, char *argv[])
{
sem_t *newsem;
int errStatus;
sharedSemas = mmap(NULL, sizeof(PGSemaphoreData), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (sharedSemas == MAP_FAILED)
{
fprintf(stderr, "mmap() failedn");
exit(EXIT_FAILURE);
}
newsem = PG_SEM_REF(sharedSemas);
if (sem_init(newsem, 1, 1) < 0)
{
fprintf(stderr, "sem_init() failedn");
exit(EXIT_FAILURE);
}
sem_wait(newsem);
/*Parent and child share mapping*/
switch (fork())
{
case -1:
fprintf(stderr, "fork() failedn");
exit(EXIT_FAILURE);
case 0:
printf("[child]mmap address %pn", sharedSemas);
do
{
printf("[child]sem_post...n");
errStatus = sem_post(newsem);
} while (errStatus < 0 && errno == EINTR);
printf("[child]exitn");
exit(EXIT_SUCCESS);
default:
printf("[parent]mmap address %pn", sharedSemas);
do
{
printf("[parent]start semi_wait...n");
errStatus = sem_wait(newsem);
} while (errStatus < 0 && errno == EINTR);
printf("[parent]stop semi_wait...n");
if (wait(NULL) == -1)
{
fprintf(stderr, "wait() failedn");
exit(EXIT_FAILURE);
}
printf("[parent]stop waiting child exitn");
if (munmap(sharedSemas, sizeof(int)) == -1)
{
fprintf(stderr, "munmap() failedn");
exit(EXIT_FAILURE);
}
printf("[parent]exitn");
exit(EXIT_SUCCESS);
}
}
// gcc -o main1 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main1.c -lpthread
执行结果:
代码语言:javascript复制[parent]mmap address 0x7f69797d4000
[parent]start semi_wait...
[child]mmap address 0x7f69797d4000
[child]sem_post...
[child]exit
[parent]stop semi_wait...
[parent]stop waiting child exit
[parent]exit
Postgresql用到信号量的场景
组提交中充当读barrier:
- CLOG中的TransactionGroupUpdateXidStatus
- ProcArrayGroupClearXid
LWLock中锁队列的唤醒:
- LWLockDequeueSelf
- LWLockAcquire
- LWLockAcquireOrWait
- LWLockWaitForVar
轻量锁是自带所队列的,等锁的进程会按顺序唤醒,等锁的进程都是等在信号量上了。