一、请知:
1. System V信号量每次执行PV操作时,都需要进行用户态和内核态的切换。
2. POSIX pthread库实现的信号量执行PV操作时,仅当需要时才进行用户态和内核态的切换。具体表述如下:
2.1 P操作:a) 在用户态“信号量值减一,且值大于等于0”,则无需陷入内核;
b) 在用户态“信号量值减一,且值小于0”,则需要陷入内核,并将调用进程插入到该信号量的等待队列,睡眠;
2.2 V操作:a) 在用户态“信号量值加一,且值大于0”,则无需陷入内核;
b) 在用户态“信号量值加一,且值小于等于0”,则需要陷入内核,并唤醒该信号量等待队列上的一个进程;
二、测试代码:
1. 使用System V信号量进行PV操作:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#define SEM_KEY 0x88991110
#define SEM_NUM 2
#define SHM_KEY 0x88991111
#define SHM_SIZE 1024
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int main(int argc, char *argv[])
{
int ret = 0;
int semid = 0, nM = 0;
union semun arg;
if (2 != argc) {
printf("usage: ./pv_sysv num.\n");
return -1;
}
nM = atoi(argv[1]);
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0600);
if (semid < 0) {
perror("semget first");
semid = semget(SEM_KEY, 0, 0);
if (semid < 0) {
perror("semget second");
return -1;
}
}
arg.val = 1;
ret = semctl(semid, 0, SETVAL, arg);
if (-1 == ret) {
perror("semctl");
}
struct timeval tmStart = {0}, tmEnd = {0};
gettimeofday(&tmStart, NULL);
struct sembuf pops[1] = {{0, -1, SEM_UNDO}};
struct sembuf vops[1] = {{0, 1, SEM_UNDO}};
for (uint64_t loop = 0; loop < 1024 * 1024 * nM; loop++) {
ret = semop(semid, pops, 1);
if (-1 == ret) {
perror("P");
}
ret = semop(semid, vops, 1);
if (-1 == ret) {
perror("V");
}
}
gettimeofday(&tmEnd, NULL);
uint64_t diff = (tmEnd.tv_sec * 1000 * 1000 + tmEnd.tv_usec) - (tmStart.tv_sec * 1000 * 1000 + tmStart.tv_usec);
printf("time: %lu us.\n", diff);
/* 只要调用过shmctl, 当连接数为0时, 该共享内存就会被删除 */
ret = semctl(semid, 0, IPC_RMID);
if (ret < 0) {
perror("shmctl");
}
return ret;
}
2. 使用POSIX pthread线程库信号量进行PV操作:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
int main(int argc, char *argv[])
{
int ret = 0, nM = 0;
sem_t *semid = NULL;
if (2 != argc) {
printf("usage: ./pv_posix num.\n");
return -1;
}
nM = atoi(argv[1]);
semid = sem_open("lfc.sem", O_CREAT | O_EXCL, 0666, 1);
if (SEM_FAILED == semid) {
perror("sem_open first");
semid = sem_open("lfc.sem", 0);
if (SEM_FAILED == semid) {
perror("sem_open second");
return -1;
}
}
struct timeval tmStart = {0}, tmEnd = {0};
gettimeofday(&tmStart, NULL);
for (uint64_t loop = 0; loop < 1024 * 1024 * nM; loop++) {
ret = sem_wait(semid);
if (0 != ret) {
perror("sem_wait");
}
ret = sem_post(semid);
if (0 != ret) {
perror("sem_post");
}
}
gettimeofday(&tmEnd, NULL);
uint64_t diff = (tmEnd.tv_sec * 1000 * 1000 + tmEnd.tv_usec) - (tmStart.tv_sec * 1000 * 1000 + tmStart.tv_usec);
printf("time: %lu us.\n", diff);
ret = sem_close(semid);
if (0 != ret) {
perror("sem_close");
}
ret = sem_unlink("lfc.sem");
if (0 != ret) {
perror("sem_unlink");
}
return ret;
}
三、测试数据:
1. 1M次PV操作:
1.1 使用pthread信号量。
root :sem$ time ./pv_posix 1
time: 48747 us.
real 0m0.067s
user 0m0.047s
sys 0m0.004s
1.2 使用System V信号量。
root :sem$ time ./pv_sysv 1
time: 1013936 us.
real 0m1.025s
user 0m0.562s
sys 0m0.445s
2. 100M次PV操作:
2.1 使用pthread信号量。
root :sem$ time ./pv_posix 100
time: 1774634 us.
real 0m1.777s
user 0m1.777s
sys 0m0.000s
2.2 使用System V信号量。
root :sem$ time ./pv_sysv 100
time: 98256266 us.
real 1m38.258s
user 0m57.293s
sys 0m40.930s
四、结论:
1. pthread库信号量的PV操作比System V信号量的PV操作具有明显的性能优化,且随着PV操作次数的增加,性能优势更加明显。
2. 实际项目中,相比于System V信号量,应尽可能多的使用pthread信号量。