信号量
基本概念
信号量多用于进程间的同步与互斥
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序。
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。
临界区:临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
临界资源:只能被一个进程同时使用(不可以多个进程共享),要用到互斥。
我们可以说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是信号量,一个进程使用互斥锁,并通知(通信)其他想要该互斥锁的进程,阻止他们的访问和使用。
基本函数
首先需要include <semaphore.h>这个库,和互斥锁一样,也是四大金刚。
sem_init
简述:创建信号量
第一个参数:指向的信号对象
第二个参数:控制信号量的类型,如果其值为0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享
第三个参数:信号量sem的初始值
返回值:success为0,failure为-1
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem_post
简述:信号量的值加1
第一个参数:信号量对象
返回值:success为0,failure为-1
int sem_post(sem_t *sem);
sem_wait
简述:信号量的值加-1
第一个参数:信号量对象
返回值:success为0,failure为-1
int sem_wait(sem_t *sem);
sem_destroy
简述:用完记得销毁哦~
第一个参数:信号量对象
返回值:success为0,failure为-1
int sem_destroy(sem_t *sem);
代码实现
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <unistd.h>
#define NUM 5
int queue[NUM];
// blank代表空位,product代表产品
sem_t blank_number, product_number;
// 生产者
void* producer(void* arg)
{
int p = 0;
while (1)
{
// 将空位信号量-1
sem_wait(&blank_number);
queue[p] = rand() % 1000 + 1;
printf(“product %d\n”, queue[p]);
// 将产品信号量+1
sem_post(&product_number);
// 一个小算法,很使用
p = (p + 1) % NUM;
sleep(rand() % 5);
}
return (void*)0;
}
// 消费者
void* consumer(void* arg)
{
int c = 0;
while (1)
{
// 等待有产品后消费,将产品信号量-1
sem_wait(&product_number);
printf(“consume %d\n”, queue[c]);
queue[c] = 0;
// 消费完毕后将空位信号量+1
sem_post(&blank_number);
c = (c + 1) % NUM;
sleep(rand() % 5);
}
return (void*)0;
}
int main(int argc, char* argv[])
{
pthread_t pid, cid;
sem_init(&blank_number, 0, NUM);
sem_init(&product_number, 0, 0);
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
sem_destroy(&blank_number);
sem_destroy(&product_number);
return 0;
}
补充。。。
进程间互斥
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <bits/mman-linux.h>
#include <string.h>
struct mt
{
int num;
pthread_mutex_t mutex;
pthread_mutexattr_t mutex_attr;
};
int main()
{
int i;
struct mt *mm;
pid_t pid;
printf("inint sucess\n");
mm = mmap(NULL,sizeof(struct mt),PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANONYMOUS,-1,0);
memset(mm,0,sizeof(*mm));
pthread_mutexattr_init(&mm->mutex_attr);
printf("inint sucess1\n");
pthread_mutexattr_setpshared(&mm->mutex_attr,PTHREAD_PROCESS_SHARED); //修改属性为进程间共享
printf("inint sucess2\n");
pthread_mutex_init(&mm->mutex,&mm->mutex_attr);
pid = fork();
if(pid == 0)
{
for(i = 0;i < 10;i++)
{
pthread_mutex_lock(&mm->mutex);
(mm->num)++;
printf("--child-------num++ %d\n",mm->num);
pthread_mutex_unlock(&mm->mutex);
}
}
else if(pid > 0)
{
for(i = 0;i < 10;i++)
{
// sleep(1);
pthread_mutex_lock(&mm->mutex);
mm->num += 2;
printf("--------parent----num+=2 %d\n",mm->num);
pthread_mutex_unlock(&mm->mutex);
}
wait(NULL);
}
pthread_mutexattr_destroy(&mm->mutex_attr);
munmap(mm,sizeof(*mm));//释放映射区
return 0;
}
wait(NULL)是一个系统调用,它会阻塞父进程,直到它的任何子进程完成. 如果子进程在父进程到达 wait(NULL) 之前终止,则子进程将转换为僵尸进程,直到其父进程等待它并将其从内存中释放. 如果父进程不等待它的子进程,并且父进程先完成,那么子进程将成为孤立进程,并被分配给 init 作为其子进程3.
总的来说就是防止子进程先于父进程退出,形成僵尸进程。
关于mmap
mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。
头文件 <sys/mman.h>
函数原型
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
mmap()[1] 必须以PAGE_SIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。
用法:
下面说一下内存映射的步骤:
用open系统调用打开文件, 并返回描述符fd.
用mmap建立内存映射, 并返回映射首地址指针start.
对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).
用munmap(void *start, size_t lenght)关闭内存映射.
用close系统调用关闭文件fd.
主要用途
UNIX网络编程第二卷进程间通信对mmap函数进行了说明。该函数主要用途有三个:
1、将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。
内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
以下是一个把普遍文件映射到用户空间的内存区域的示意图。
文件锁,fcntl和位图
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
int main(int argc,char *argv[])
{
int fd;
struct flock f_lock;
if(argc < 2)
{
printf("./file_lock1 filename\n");
exit(1);
}
if((fd = open(argv[1],O_RDWR)) < 0)
{
printf("open err");
exit(1);
}
f_lock.l_type = F_WRLCK; //设置写锁
// f_lock.l_type = F_RDLCK; //设置读锁
f_lock.l_whence = SEEK_SET;
f_lock.l_start = 0;
f_lock.l_len = 0;
fcntl(fd,F_SETLKW,&f_lock);
printf("get flock\n");
sleep(10);
f_lock.l_type = F_UNLCK;
fcntl(fd,F_SETLKW,&f_lock);
printf("un flock\n");
close(fd);
return 0;
}
总结:代码中,采用读锁,则启动俩个进程后都可以读。
采用写锁,等待进程1结束,释放写锁,进程2才可以加写锁。