信号量是解决进程之间的同步与互斥的IPC机制,互斥与同步关系存在的根源在于临界资源。临界资源是在同一个时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,临界资源包括:硬件资源(处理器、内存、存储器以及其他外围设备等)和软件资源(共享代码段、共享结构和变量等)。
信号量是描述某一种资源是否可用的变量,信号量的值表示当前可用的资源的数量,若信号量的值等于0则意味着目前没有可用的资源。
对信号量进行的两个原子操作(PV操作)
P操作:等待。如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。
V操作:发送信号。如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。
Linux系统继承了Unix系统的两种信号量:
A、内核信号量,由内核控制路径使用
B、用户态进程使用的信号量,分为POSIX信号量和SYSTEM V信号量。POSIX信号量又分为有名信号量和无名信号量,有名信号量,其值保存在文件中, 可用于线程、进程间的同步;无名信号量,其值保存在内存中。
POSIX信号量与SYSTEM V信号量的区别如下:
A、POSIX信号量是个非负整数,常用于线程间同步。SYSTEM V信号量是一个或多个信号量的集合,是一个信号量结构体,信号量是它的一部分,常用于进程间同步。
B、POSIX信号量的引用头文件是<semaphore.h>,而SYSTEM V信号量的引用头文件是<sys/sem.h>。
C、System V信号量是复杂的,而POSIX信号量是简单的。
无名信号量的创建与普通变量一样,声明后初始化即可,例如:sem_t semid = 1。无名信号量常用于多线程间的同步,也可用于相关进程间的同步。无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,无名信号量的值保存在内存中。
常见的无名信号量相关函数:
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared==0 用于同一多线程的同步;
pshared>0 用于多个相关进程间的同步(即由fork产生的)
int sem_destroy(sem_t *sem);
销毁信号量
int sem_getvalue(sem_t *sem, int *sval);
取回信号量sem的当前值,把值保存到sval中。
若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
1) 返回0
2) 返回阻塞在该信号量上的进程或线程数目
linux采用返回的第一种策略。
sem_wait(或sem_trywait)相当于P操作,即申请资源。
int sem_wait(sem_t *sem); // 阻塞的函数
测试所指定信号量的值,它的操作是原子的。
若sem>0,那么它减1并立即返回。
若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。
int sem_trywait(sem_t *sem); // 非阻塞的函数
其他的行为和sem_wait一样,除了:
若sem==0,不是睡眠,而是返回一个错误EAGAIN。
sem_post相当于V操作,释放资源。
int sem_post(sem_t *sem);
把指定的信号量sem的值加1;
呼醒正在等待该信号量的任意线程。
无名信号量的常见用法是将要保护的变量放在sem_wait和sem_post中间所形成的临界区内。
使用信号量实现多线程同步(线程执行顺序随机)实例:
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>int number; // 被保护的全局变量
sem_t semid;void* thread_one(void *arg)
{sem_wait(&semid);printf("thread_one have the semaphoren");number++;printf("number = %dn",number);sem_post(&semid);
}void* thread_two(void *arg)
{sem_wait(&semid);printf("thread_two have the semaphore n");number--;printf("number = %dn",number);sem_post(&semid);
}int main(int argc, char *argv[])
{number = 1;pthread_t tid1, tid2;sem_init(&semid, 0, 1);pthread_create(&tid1,NULL,thread_one, NULL);pthread_create(&tid2,NULL,thread_two, NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("n");return 0;
}
使用信号量实现多线程同步(线程执行顺序指定)实例:
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>int number; // 被保护的全局变量sem_t semid1, semid2;void* thread_one(void *arg)
{sem_wait(&semid1);printf("thread_one have the semaphoren");number++;printf("number = %dn",number);sem_post(&semid1);
}void* thread_two(void *arg)
{sem_wait(&semid2);printf("thread_two have the semaphore n");number--;printf("number = %dn",number);sem_post(&semid2);
}int main(int argc, char *argv[])
{number = 1;pthread_t tid1, tid2;sem_init(&semid1, 0, 1);sem_init(&semid2, 0, 0);pthread_create(&tid1,NULL,thread_one, NULL);pthread_create(&tid2,NULL,thread_two, NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);printf("n");sem_destroy(&semid1);sem_destroy(&semid2);return 0;
}
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.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, nloop=10, zero=0, *ptr;sem_t mutex;//open a file and map it into memoryfd = open(",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, initialize semaphore */if( sem_init(&mutex,1,1) < 0) //{perror("semaphore initilization");exit(0);}if (fork() == 0){ /* child process*/sem_wait(&mutex);for (i = 0; i < nloop; i++){printf("child: %dn", (*ptr)++);}sem_post(&mutex);exit(0);}/* back to parent process */sem_wait(&mutex);for (i = 0; i < nloop; i++){printf("parent: %dn", (*ptr)++);}sem_post(&mutex);exit(0);
}
有名信号量的特点是把信号量的值保存在文件中,既可以用于线程,也可以用于亲缘进程间,无亲缘进程间。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name是文件的路径名,在linux中sem都是创建在/dev/shm目录下。name可以写成“/mysem”或“mysem”,创建出来的文件都是“/dev/sem”,千万不要写路径。
oflag 有O_CREAT或O_CREAT|EXCL两个取值;
mode控制新的信号量的访问权限;
value指定信号量的初始化值。
int sem_getvalue(sem_t *sem, int *sval);
取回信号量sem的当前值,把值保存到sval中。
若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
1) 返回0
2) 返回阻塞在该信号量上的进程或线程数目
linux采用返回的第一种策略。
sem_wait(或sem_trywait)相当于P操作,即申请资源。
int sem_wait(sem_t *sem); // 阻塞的函数
测试所指定信号量的值,它的操作是原子的。
若sem>0,那么它减1并立即返回。
若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。
int sem_trywait(sem_t *sem); // 非阻塞的函数
其他的行为和sem_wait一样,除了:
若sem==0,不是睡眠,而是返回一个错误EAGAIN。
sem_post相当于V操作,释放资源。
int sem_post(sem_t *sem);
把指定的信号量sem的值加1;
呼醒正在等待该信号量的任意线程。
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
SYSTEM V信号量是SYSTEM V IPC的组成部分,System V信号量在内核中维护,其中包括二值信号量 、计数信号量、计数信号量集。
系统中记录信号量的数据结构(struct ipc_ids sem_ids)位于内核中,系统中的所有信号量都可以在结构sem_ids中找到访问入口。
struct semid_ds {struct ipc_permsem_perm ;struct sem* sem_base ; //信号数组指针ushort sem_nsem ; //此集中信号个数time_t sem_otime ; //最后一次semop时间time_t sem_ctime ; //最后一次创建时间
} ;
信号量编程的流程:
调用semget()函数,不同进程使用同一个信号量键值来获得同一个信号量
#include <sys/sem.h>int semget (key_t key, int nsem, int oflag) ;
返回值是一个称为信号量标识符的整数,semop和semctl函数将使用它。
key:所创建或打开信号量集的键值。需要是唯一的非零整数。
nsem:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。一般取值为1.
oflag:调用函数的操作类型,也可用于设置信号量集的访问权限,SEM_R(read)和SEM_A(alter),也可以是IPC_CREAT或IPC_EXCL
使用semctl()函数的SETVAL操作
当使用二维信号量时,通常将信号量初始化为1
int semctl(int semid, int semnum, int cmd, ...);
sem_id是由semget返回的信号量标识符。
sem_num是信号量集中的某一个资源
cmd:表示将要采取的动作。最常用的两个值如下:
SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置。其作用是在信号量第一次使用之前对它进行设置。
IPC_RMID:用于删除一个无需继续使用的信号量标志符。
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 */
};
调用semop()函数
实现进程之间的同步和互斥的核心部分
#include <sys/sem.h>int semop (int semid, struct sembuf * opsptr, size_t nops) ;
参数semid是一个通过semget函数返回的一个信号量标识符
参数opsptr是一个指针,指向一个信号量操作数组
参数nops标明了参数semoparray所指向数组中的元素个数
struct sembuf{ //除非使用一组信号量,否则它为0 short sem_num;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,// 一个是+1,即V(发送信号)操作。 short sem_op;short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号, //并在进程没有释放该信号量而终止时,操作系统释放信号量
};
使用semclt()函数的IPC_RMID操作,在程序中不应该出现对已被删除的信号量的操作。
本文发布于:2024-01-31 22:16:29,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170671059131746.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |