多线程详解2-信号量,进程互斥量,文件锁,fcntl和位图

信号量

基本概念

信号量多用于进程间的同步与互斥
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序。
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。

临界区:临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
临界资源:只能被一个进程同时使用(不可以多个进程共享),要用到互斥。
我们可以说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是信号量,一个进程使用互斥锁,并通知(通信)其他想要该互斥锁的进程,阻止他们的访问和使用。
在这里插入图片描述

基本函数

首先需要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才可以加写锁。

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玖玖玖_violet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值