信号量
/*********************
信号量就相当于共享内存中的一个盒子,
把信号量初始化成多少就是最开始的时候往盒子里放了多少个信号!
P操作就是从盒子里拿信号,只有盒子里有信号时才能拿,
若盒子里没有信号,你就只能在盒子旁边等到什么时候盒子里有信号了,拿到信号才能走!
V操作就是往盒子里放信号,不管盒子里有没有信号,你都可以往里面放信号!
*********************/
一.【信号量是】在计算机中用来模拟信号灯的一个【整数】
当这个整数变为非0才能进行某种操作许可;
在进行操作的同时,将该整数减1,以此改变信号灯,
将减1操作称为P操作,也称获取信号;
当该操作进行完之后,将该整数加1,以此来恢复信号灯,
将加1操作称为V操作,也称释放信号;
信号量的原理可以模拟为一下代码:
int i=N; //初始化信号量
if(i != 0)
{
i--; //可以进行P操作
//i++ //也可以进行V操作
进行某种操作.....
i++; //V操作
}
else if(i == 0)
{
i++; //只能进行V操作
}
二.进程中使用信号量:
1.获取key,使用函数ftok()
key_t key=ftok("/home",'a');
"/home":字符串形式的文件路径,要求必须存在且可访问
'a' :整型的项目ID,要求非0,低8位被使用,一般写一个字符
2.创建/获取信号量,使用函数semget()函数
int semid = semget(key,1,0777 | IPC_CREAT);
key: ftok的返回值
1: 信号量的个数
0777 | IPC_CREAT: 标志,可用 IPC_CREAT IPC_EXCL
注:创建新的信号量时,需要指定权限,如0664 | IPC_CREAT
3.初始化信号量,使用函数semctl()函数
int res = semctl(semid,0,SETVAL,1);
if(res < 0)
{
perror("semctl");
exit(-1);
}
semid:semid,semget的返回值
0:信号量的下标
SETVAL:操作命令
IPC_RMID 删除信号量,不需要第四个参数
IPC_SETVAL 使用第4个参数的值初始化信号量
1:可变参数,是否需要由第三参数决定
4.定义结构体指针
struct sembuf
{
unsigned short sem_num; /* 信号量所在下标 */
short sem_op; /* 具体操作,正数增加,负数减少*/
short sem_flg; /* 标志 IPC_NOWAIT and SEM_UNDO. */
};
5.初始化结构体指针
P操作:
sb.sem_num = 0;
sb.sem_op = -1; //P操作将其初始化为-1
sb.sem_flg = SEM_UNDO;
V操作:
sb.sem_num = 0;
sb.sem_op = 1; //V操作将其初始化为1
sb.sem_flg = SEM_UNDO;
6.操作信号量,使用函数semop()函数
int res = semop(semid,&sb,1);
if(res < 0)
{
perror("semop");
exit(-1);
}
semid:获取的信号量
&sb:结构体指针
struct sembuf
{
unsigned short sem_num; /* 信号量所在下标 */
short sem_op; /* 具体操作,正数增加,负数减少*/
short sem_flg; /* 标志 IPC_NOWAIT and SEM_UNDO. */
};
1:结构体指针指向的结构体数组的元素个数
ps:访问共享资源
7.删除信号量,使用函数semctl()函数
int semctl(semid,0,IPC_RMID);
semid:获取的信号量
0:信号量的下标
IPC_RMID:删除信号量,不需要第四个参数代码实例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
struct sembuf sb;
char *s="exit";
void p(int semid)
{
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO;
int res = semop(semid,&sb,1);
if(res < 0)
{
perror("sem_op p");
exit(-1);
}
return ;
}
void v(int semid)
{
sb.sem_num = 0;
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
int res = semop(semid,&sb,1);
if(res < 0)
{
perror("sem_op v");
exit(-1);
}
return ;
}
int main()
{
//1.生成key
key_t key=ftok("/home",'a');
if(key < 0)
{
perror("ftok");
exit(-1); //直接结束进程
}
//2.1 创建信号量
int semid = semget(key,1,0777 | IPC_CREAT);
if(semid < 0)
{
perror("semget");
exit(-1);
}
//2.2创建共享内存
int shmid = shmget(key,4096,0777 | IPC_CREAT);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
//3.1 初始化信号量
int res = semctl(semid,0,SETVAL,1);
if(res < 0)
{
perror("semctl");
exit(-1);
}
//3.2 映射共享内存
void *addr = (void *)shmat(shmid,NULL,0);
//0是标志,代表可读可写
if(addr == (void *)-1)
{
perror("shmat");
exit(-1);
}
//4.创建进程
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
else if(pid == 0)
{ //子进程中接收并打印共享内存中的数据
while(1)
{
if(!strcmp(addr,s))
{
//8.解除子进程的映射
shmdt(addr);
//9.删除共享内存
shmctl(shmid,IPC_RMID,NULL);
break;
}
//6.打印共享内存中数据
p(semid);
printf("semid buf = %s\n",(char *)addr);
v(semid);
}
exit(0);
}
else if(pid > 0)
{ //父进程中写数据存入共享内存
while(1)
{
//5.写数据存入共享内存
p(semid);
char buf[1024] = {0};
scanf("%s",buf); //写数据存入缓存区
memcpy(addr,buf,sizeof(buf)); //将缓存区中数据拷贝到共享内存中
v(semid);
printf("buf = %s\n",buf); //打印要写入的数据
if(!strcmp(buf,s))
{
break;
}
}
//7.解除父进程的映射
shmdt(addr);
wait(NULL);
}
return 0;
}
}
三.线程中使用信号量
1.定义一个信号量
sem_t sem;
2.初始化信号量 sem_init()函数
sem_init(&sem,0,1);
&sem: 定义的信号量的地址
第二个参数:0:表示同一进程中的线程间共享,非0:进程间共享
1: 信号量的初始值
注:sem处信号量为同一进程中线程共享的信号量,
信号的初始值为1,即P、V操作从1开始
3.获取信号量(即P操作)
sem_wait(&sem);
&sem:定义的信号量的地址
ps:访问共享资源
4.释放信号量(即V操作)
sem_post(&sem);
&sem:定义的信号量的地址
5.销毁信号量
sem_destroy(&sem);
&sem:定义的信号量的地址代码实例:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include <semaphore.h>
int count = 0;
sem_t sem;//定义信号量
//线程函数
void *thread_func(void *arg)
{
sem_wait(&sem); //获取信号量
int *p = (int*)arg; //获得线程创建时传进来的参数
int num = *p;
printf("child num = %d\n",num);
int i;
for(i = 0; i < num; i++)
{
count++;
}
sem_post(&sem); //释放信号量
}
int main()
{
int i;
int num = 1000000;
int res;
pthread_t tid[2];
sem_init(&sem,0,1); //初始化信号量
//创建两个线程,线程ID保存在数组tid中
for(i = 0; i < 2; i++)
{
res = pthread_create(&tid[i],NULL,thread_func,(void*)&num);
if(res != 0)
{
fprintf(stderr,"thread create error: %s\n",strerror(res));
exit(-1);
}
printf("creat new thread id = %lu\n",tid[i]);
}
//等待线程结束
for(i = 0; i < 2; i++)
{
pthread_join(tid[i],NULL);
printf("thread %d exited!!\n",i);
}
printf("count = %d\n",count);
sem_destroy(&sem); //不再使用,销毁信号量
return 0;
}
【用多个信号量控制访问的先后顺序】:
sem_t sem1;
sem_t sem2;
1.将需要优先访问的线程/进程初始化成非0;
sem_init(&sem1,0,1);
2.将后访问的线程/进程初始化成0;
sem_init(&sem2,0,0);
3.在优先访问的线程/进程的访问过程中对需要后访问的线程/进程
进行V操作
sem_wait(&sem1);
执行任务。。。。。
任务完成
sem_post(&sem2);
4.此时后访问的进程才可以访问,在访问过程中对需要先访问的线程/进程
进行V操作
sem_wait(&sem2);
sem_post(&sem1);
【程序执行流程】:先执行sem1,碰到sem_post(&sem2);后,
sem1休眠,立即执行sem2,碰到sem_post(&sem1);后,
继续执行后面的任务,执行完后再去执行sem1中sem_post(&sem2);后面的程序