生产者消费者问题 多进程共享内存

问题描述

  • 一个大小为3的缓冲区,初始为空

  • 2个生产者随机等待一段时间,往缓冲区添加数据,若缓冲区已满,等待消费者取走数据后再添加,重复6次

  • 3个消费者随机等待一段时间,从缓冲区读取数据,若缓冲区为空,等待生产者添加数据后再读取,重复4次

说明:

  • 显示每次添加和读取数据的时间及缓冲区里的数据
  • 生产者和消费者用进程模拟

思路

这道题目涉及到的知识点有:

  • 进程控制管理,包括进程的创建与销毁等
  • 进程通信技术,如管道、共享内存等

解决思路主要是:

  • 一个主进程负责创建和销毁子进程,负责创建共享内存区和公用信号量
  • 五个子进程,两个是生产者,三个是消费者,通过信号量对共享内存进行互斥读写

创建互斥访问量

创建信号量如下:

信号量:EMPTY, FILLED, RW
初始化:EMPTY=3,FILLED=0,RW=1

说明:

EMPTY信号量指示缓冲区有多少个空位置没有被占用,因此初始值等于缓冲区数量;

FILLED指示缓冲区有多少个位置被占用,因此初始值等于0;

RW指示是否允许对共享内存进行读写操作,为防止进程并发执行导致的数据共享错误,每次仅允许一个进程对共享内存进行操作,因此初始值为1.

生产者消费者执行操作

生产者

P(EMPTY);//首先询问是否有空闲缓冲区,没有则阻塞,等待消费者拿出数据释放一个缓冲区;有则EMPTY-=1
P(RW);//是否可以对共享内存进行读写操作
WRITE();//写入数据
V(RW);//释放读写锁
V(FILLED);//这里要注意释放的不是EMPTY,因为生产者是消耗EMPTY空间的,
//每消耗一个EMPTY则有一个缓冲区被占用,故而FILLED+=1

消费者

P(FILLED);//首先询问缓冲区里面是否有数据
P(RW);
READ();
V(RW);
V(EMPTY);

无论是Linux还是Windows,对共享内存的访问基本都是符合上述规则的,只是具体实现和API调用不同。Windows进程管理和共享内存管理相比于Linux要复杂的多,当然也提供了更为有效的异常处理机制。下面介绍Linux下和Windows具体实现的API。

关于进程创建和管理不再赘述,主要讲解共享内存和信号量方面的内容。

共享内存

所谓共享内存,是指不同进程间出于通信或者避免冗余信息拷贝而申请的可以被不同进程共同访问的内存区域。

Linux

在Linux平台上,相关API是由POSIX提供的。主要API有shmat,shmctl,shmget.

其中shmget用于创建共享内存;shmat用于将共享内存链接到当前进程地址空间中来;shmdt用于将共享内存从当前进程分离;shmctl用于控制共享内存,可以销毁共享内存。

1. shmget

int shmget(key_t key, size_t size, int shmflg);
  • key_t key 第一个参数是用于共享内存命名,不同进程通过key进行共享内存识别。需要指出的是,shmget并不只是用于创建共享内存。这里返回的描述符有可能是已经创建好的命名为key的共享内存的描述符。具体shmget的作用以及共享内存的权限由shmflg指定。
  • size_t size 指定需要的共享内存空间大小
  • int shmflg 权限标志。如果想要在key标识的共享内存不存在时创建的话可以使用IPC_CREAT进行指示,如果还要指定该共享内存的权限的话(该权限含义与Linux下普通文件的含义相同),可以与权限做或运算。如要创建新的共享内存且指定共享内存的权限为0644(表示该进程创建的共享内存对于该用户创建的其他内存而言具有rw权限,同用户组以及其他用户权限均只有r权限),则可以指定shmflgIPC_CREAT | 0644
  • 执行成功则返回一个与key相关的共享内存描述符(正整数),用户需要记录该描述符以后续使用。调用失败返回-1

2. shmat

第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);
  • shm_id就是shmget函数返回的描述符
  • const void *shm_addr指定共享内存链接到当前进程中的地址位置,通常为空,让系统自己选择
  • int shmflg是一组标志位,常为0
  • 调用成功则返回一个指向共享内存首地址,调用失败则返回值为(void*)-1

3. shmdt

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void* shmaddr);
  • const void *shaddr要分离的共享内存首地址,即是shmat函数的返回值

  • 调用成功时返回0,失败时返回-1

4. shmctl

控制共享内存,它的原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);
  • int shm_idshmget函数返回的共享内存描述符

  • int command 要采取的操作

  • struct shmid_ds *buf 是一个结构体指针,指向共享内存的shmid_ds结构至少包括以下成员:

    struct shmid_ds
    {
        uid_t shm_perm.uid;
        uid_t shm_perm.gid;
        mode_t shm_perm.mode;
    };
    

Windows

1. CreateFileMapping

通过这个API函数 将创建一个内存映射文件的内核对象,用于映射文件到内存。与虚拟内存一样,内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该区域。它们之间的差别是,物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件。

HANDLE CreateFileMapping(
  HANDLE hFile,              // handle to file to map
  LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
                             // optional security attributes
  DWORD flProtect,           // protection for mapping object
  DWORD dwMaximumSizeHigh,   // high-order 32 bits of object size
  DWORD dwMaximumSizeLow,    // low-order 32 bits of object size
  LPCTSTR lpName             // name of file-mapping object
);
  • hFile:用于标识你想要映射到进程地址空间中的文件句柄。该句柄可以通过调用CreateFile函数返回。这里,我们并不需要一个实际的文件,所以,就不需要调用 CreateFile 创建一个文件, hFile 这个参数可以填写 INVALID_HANDLE_VALUE

  • lpFileMappingAttributes:参数是指向文件映射内核对象的 SECURITY_ATTRIBUTES结构的指针,通常传递的值是 NULL

  • flProtect:对内存映射文件的安全设置

    • PAGE_READONLY 以只读方式打开映射;
    • PAGE_READWRITE 以可读、可写方式打开映射;
    • PAGE_WRITECOPY 为写操作留下备份
  • dwMaximumSizeHigh:文件映射的最大长度的高32位。

  • dwMaximumSizeLow:文件映射的最大长度的低32位。如这个参数和dwMaximumSizeHigh都是零,就用磁盘文件的实际长度。

  • lpName:指定文件映射对象的名字,别的进程就可以用这个名字去调用 OpenFileMapping 来打开这个 FileMapping 对象。

  • 如果创建成功,返回创建的内存映射文件的句柄,如果已经存在,则也返回其句柄,但是调用 GetLastError()返回的错误码是:183(ERROR_ALREADY_EXISTS),如果创建失败,则返回NULL

2. MapViewOfFile

如果调用CreateFileMapping成功,则调用MapViewOfFile函数,将内存映射文件映射到进程的虚拟地址中

LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,  // file-mapping object to map into 
                              // address space
  DWORD dwDesiredAccess,      // access mode
  DWORD dwFileOffsetHigh,     // high-order 32 bits of file offset
  DWORD dwFileOffsetLow,      // low-order 32 bits of file offset
  DWORD dwNumberOfBytesToMap  // number of bytes to map
);
  • hFileMappingObjectCreateFileMapping()返回的文件映像对象句柄。
  • dwDesiredAccess: 映射对象的文件数据的访问方式,而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。
  • dwFileOffsetHigh: 表示文件映射起始偏移的高32位.
  • dwFileOffsetLow: 表示文件映射起始偏移的低32位.
  • dwNumberOfBytesToMap :文件中要映射的字节数。为0表示映射整个文件映射对象。

3. OpenFileMapping

在数据接收进程中,首先调用OpenFileMapping()函数打开一个命名的文件映射内核对象,得到相应的文件映射内核对象句柄hFileMapping;如果打开成功,则调用MapViewOfFile()函数映射对象的一个视图,将文件映射内核对象hFileMapping映射到当前应用程序的进程地址,进行读取操作。(当然,这里如果用CreateFileMapping也是可以获取对应的句柄)

HANDLE OpenFileMapping(
  DWORD dwDesiredAccess,  // access mode
  BOOL bInheritHandle,    // inherit flag
  LPCTSTR lpName          // pointer to name of file-mapping object
);
  • dwDesiredAccess:同MapViewOfFile函数
  • dwDesiredAccess参数
  • bInheritHandle :如这个函数返回的句柄能由当前进程启动的新进程继承,则这个参数为TRUE
  • lpName :指定要打开的文件映射对象名称。

4. 清理内核对象

//取消本进程地址空间的映射;   
UnmapViewOfFile(pLocalMem);  
      
pLocalMem=NULL;   
//关闭文件映射内核文件  
CloseHandle(hFileMapping);

信号量

为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。

Linux

1. semget

创建一个新信号量或取得一个已有信号量

int semget(key_t key, int num_sems, int sem_flags);
  • key_t key 信号量命名,不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
  • num_sems 需要的信号量数目
  • sem_flags 是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
  • 成功返回一个相应信号标识符(非零),失败返回-1

2. semop

它的作用是改变信号量的值,原型为:

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
  • int sem_id 是由semget 返回的信号量描述符

  • struct sembuf定义如下

    struct sembuf{
        short sem_num;//信号量组中要修改的信号量下标
        short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                        //一个是+1,即V(发送信号)操作。
        short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                        //并在进程没有释放该信号量而终止时,操作系统释放信号量
    };
    
  • size_t num_sem_ops 要修改的信号量数量

3. semctl

改变信号量的值,原型为:

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
  • int sem_id 信号量组key

  • struct sembuf

    struct sembuf{
        short sem_num;//除非使用一组信号量,否则它为0
        short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                        //一个是+1,即V(发送信号)操作。
        short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                        //并在进程没有释放该信号量而终止时,操作系统释放信号量
    };
    
  • size_t num_sem_ops 要修改的信号量的数量

Windows

1. CreateSemaphore

创建信号量

HANDLE CreateSemaphore(

  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

  LONG lInitialCount,

  LONG lMaximumCount,

  LPCTSTR lpName

);
  • lpSemaphoreAtributes 安全控制,一般直接传入NULL
  • llnitialCount 初始资源数量
  • lMaximumount 最大并发数量
  • lpName 信号量的名称,传入NULL表示匿名信号量

2. OpenSemaphore

打开信号量

HANDLE OpenSemaphore(

  DWORD dwDesiredAccess,

  BOOL bInheritHandle,

  LPCTSTR lpName

);
  • dwDesiredAccess 访问权限对一般传入SEMAPHORE_ALL_ACCESS
  • blnheritHandlw 信号量句柄继承性,一般传入TRUE即可
  • lpName 名称,不同进程的各线程可以通过名称来确保它们访问同一个信号量

3. ReleaseSemaphore

递增信号量的当前资源计数

BOOL ReleaseSemaphore(

  HANDLE hSemaphore,

  LONG lReleaseCount,  

  LPLONG lpPreviousCount 

);
  • hSemaphore 信号量句柄
  • lReleaseCount 表示增加个数,必须大于0且不超过最大资源数量
  • lpPreviousCount 可以用来传出先前的资源计数,设为NULL表示不需要传出

源代码实现

Linux

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

#define NUM_OF_RESOURCE 3

//共享内存缓冲区资源数据结构
typedef struct BUFF
{
    int resource[NUM_OF_RESOURCE];
    int from_ptr;
    int end_ptr;
} RESOURCE;
//简易的先进先出循环队列

//信号量数据结构
union semun {
    int value;
};

//用于映射信号量编号与对应意义的映射
enum MUTEX
{
    EMPTY,
    FILLED,
    RW_MUX,
    NUM_MUX
};

//信号量集合描述符
int semid;
//共享空间描述符
int shmid;
//共享空间首地址
RESOURCE *shm;

//绑定共享内存空间
void attach_shm()
{
    //将共享内存连接到当前进程的地址空间
    shm = (RESOURCE *)shmat(shmid, NULL, 0);
    if (shm == (RESOURCE *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
    // printf("Memory attached at %X\n", shm);
}

//解绑共享内存空间
void detach_shm()
{
    //把共享内存从当前进程中分离
    if (shmdt((void *)shm) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
}

//获取资源
//对对应信号量实施P操作
void P(short unsigned int num)
{
    struct sembuf sb =
        {
            num, -1, 0 //0表示信号量编号,-1表示P操作,SEM_UNDO表示进程退出后,该进程对sem进行的操作将被撤销
        };
    //修改集合中,一个或多个信号量值
    semop(semid, &sb, 1);
}

//释放资源
//对对应信号量实施V操作
void V(short unsigned int num)
{
    struct sembuf sb = {
        num, 1, 0 //
    };
    semop(semid, &sb, 1);
}

// 消费者从共享内存区读取数据
int read_resource()
{
    int result = 0;
    P(FILLED);
    P(RW_MUX);
    result = shm->resource[shm->from_ptr];
    shm->from_ptr++;
    if (shm->from_ptr >= NUM_OF_RESOURCE)
    {
        shm->from_ptr = 0;
    }
    V(RW_MUX);
    V(EMPTY);
    return result;
}

// 生产者向共享内存区写入数据
int write_resource()
{
    srand((unsigned int)time(NULL));
    int result = rand() % 100 + 11;
    P(EMPTY);
    P(RW_MUX);
    shm->resource[shm->end_ptr] = result;
    shm->end_ptr++;
    if (shm->end_ptr >= NUM_OF_RESOURCE)
    {
        shm->end_ptr = 0;
    }
    V(RW_MUX);
    V(FILLED);
    return result;
}

// 生产者
void product(int seed)
{
    attach_shm();
    for (int i = 0; i < 6; i++)
    {
        srand((unsigned int)time(NULL) + seed);
        sleep(rand() % 2 + 1);
        time_t start_process_time = time(NULL);
        printf("productor #%2d, write data %3d at %s", getpid(), write_resource(), ctime(&start_process_time));
    }
    detach_shm();
}

// 消费者
void consume(int seed)
{
    attach_shm();
    for (int i = 0; i < 4; i++)
    {
        srand((unsigned int)time(NULL) + seed);
        sleep(rand() % 4 + 1);
        time_t start_process_time = time(NULL);
        printf("consumer  #%2d, read  data %3d at %s", getpid(), read_resource(), ctime(&start_process_time));
    }
    detach_shm();
}

int main(int argc, char const *argv[])
{
    //记录父进程pid
    pid_t ppid = getpid();

    //信号集名字,信号集中信号量的个数,信号量集合的权限
    semid = semget((key_t)1234, NUM_MUX, IPC_CREAT | 0600); //创建信号量
    if (semid == -1)
    {
        perror("semget");
    }

    // 初始化信号量
    semun s;
    s.value = NUM_OF_RESOURCE; //初始时,缓冲区全为空
    semctl(semid, EMPTY, SETVAL, s);
    s.value = 0;//初始时,缓冲区为空,没有被填充的数据块
    semctl(semid, FILLED, SETVAL, s);
    s.value = 1;//刚开始时允许进行读写操作
    semctl(semid, RW_MUX, SETVAL, s);

    //创建共享内存
    shmid = shmget((key_t)1234, sizeof(RESOURCE), 0666 | IPC_CREAT);
    if (shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
    attach_shm();
    //初始化共享内存
    for (int i = 0; i < NUM_OF_RESOURCE; i++)
    {
        shm->resource[i] = 0;
    }
    shm->from_ptr = 0;
    shm->end_ptr = 0;

    //创建六个进程——一个父进程+两个生产者+三个消费者
    pid_t child_pid[5];
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        child_pid[i] = fork();
        if (child_pid[i] == 0) //子进程
        {
            break;
        }
    }

    if (i < 2) //生产者
    {
        product(i);
    }
    else if (i < 5) //消费者
    {
        consume(i);
    }

    if (getpid() == ppid)//父进程
    {
        // printf("I am parent processe: #%d\n", ppid);
        for (int i = 0; i < 5; i++)//等待子进程结束
        {
            waitpid(child_pid[i], NULL, 0);
        }
        detach_shm();
        // 删除共享内存
        if (shmctl(shmid, IPC_RMID, 0) == -1)
        {
            fprintf(stderr, "shmctl(IPC_RMID) failed\n");
            exit(EXIT_FAILURE);
        }
    }
    return 0;
}

运行输出

1575637491394

Windows

#include <iostream>
#include <string>
#include <wchar.h>
#include <windows.h>
#include <tchar.h>
#include <time.h>
#define NUM_OF_CONSUMER 3
#define NUM_OF_PRODUCTOR 2
#define NUM_OF_PROCESS 5
#define NUM_OF_RESOURCE 3
using namespace std;

//共享内存缓冲区数据结构
typedef struct BUF
{
	int resource[NUM_OF_PROCESS];
	int from_ptr;
	int end_ptr;
} RESOURCE;
//简易的先入先出循环队列

//子进程handle
HANDLE handle_of_process[NUM_OF_PROCESS];
// 子进程主线程
HANDLE handle_of_thread[NUM_OF_PROCESS];
string shared_mem_name("SharedMemory");
string empty_mux_name("EMPTY_MUX");
string filled_mux_name("FILLED_MUX");
string rw_mux_name("RW_MUX");
string print_mux_name("PRINT_MUX");

//print_mux用于互斥打印,防止出现输出混乱
HANDLE empty_mux, filled_mux, rw_mux, print_mux;
//共享内存映射
HANDLE h_map = NULL;
//共享内存首地址
RESOURCE *shm;

//创建子进程
void newProcess(int n_clone);
//创建信号量作为锁
void create_mux();
//在子进程打开信号量
void open_mux();
//关闭信号量
void close_mux();
//绑定共享内存
void attach_shm();
//解绑定共享内存
void detach_shm();
//消费者
void consume(int n_clone);
//生产者
void product(int n_clone);
//生产者向共享内存写入
int write_resource();
//消费者从共享内存读出
int read_resource();
//对信号量进行P操作
void P(HANDLE &mutex);
//对信号量进行V操作
void V(HANDLE &mutex);

int main(int argc, TCHAR *argv[])
{

	if (argc == 1) //父进程不传参,因此argc == 1
	{
		//创建共享内存
		h_map = ::CreateFileMapping(INVALID_HANDLE_VALUE,
									NULL,
									PAGE_READWRITE,
									0,
									sizeof(RESOURCE),
									shared_mem_name.c_str());
		if (h_map == NULL)
		{
			perror("CreateFileMapping");
			return 0;
		}
		shm = (RESOURCE *)MapViewOfFile(
			h_map,
			FILE_MAP_ALL_ACCESS,
			0,
			0,
			0);
		ZeroMemory(shm, sizeof(RESOURCE));

		create_mux();

		for (int i = 0; i < NUM_OF_PROCESS; i++)
		{
			newProcess(i);
		}

		WaitForMultipleObjects(NUM_OF_PROCESS, handle_of_process, true, INFINITE);

		for (int i = 0; i < NUM_OF_PROCESS; i++)
		{
			CloseHandle(handle_of_thread[i]);
			CloseHandle(handle_of_process[i]);
		}
		detach_shm();
		//关闭共享内存区
		CloseHandle(h_map);
		close_mux();
	}
	else //子进程传入一个参数
	{
		int n_clone = atof(argv[1]);
		if (n_clone < NUM_OF_PRODUCTOR) //生产者
		{
			product(n_clone + 1);
		}
		else if (n_clone < NUM_OF_PROCESS) //消费者
		{
			consume(n_clone + 1);
		}
	}

	return 0;
}

void newProcess(int n_clone)
{
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));

	TCHAR application[MAX_PATH];
	//获取当前进程加载程序所在目录
	GetModuleFileName(NULL, application, sizeof(application));
	TCHAR cmd_line[MAX_PATH];
	sprintf(cmd_line, "\"%s\" %d", application, n_clone);

	// printf("application: %s\n",application);
	// printf("cmd_line: %s\n", cmd_line);

	if (CreateProcess(
			NULL, //lpApplicationName.若为空,则lpCommandLine必须指定可执行程序
			//若路径中存在空格,必须使用引号框定
			cmd_line, //lpCommandLine
			//若lpApplicationName为空,lpCommandLine长度不超过MAX_PATH
			NULL,  //指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承,进程安全性
			NULL,  //	如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。<同上>,线程安全性
			false, //	指示新进程是否从调用进程处继承了句柄。句柄可继承性
			0,	 //	指定附加的、用来控制优先类和进程的创建的标识符(优先级)
			//	CREATE_NEW_CONSOLE	新控制台打开子进程
			//	CREATE_SUSPENDED	子进程创建后挂起,直到调用ResumeThread函数
			NULL, //	指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。指向环境字符串
			NULL, //	指定子进程的工作路径
			&si,  //	决定新进程的主窗体如何显示的STARTUPINFO结构体
			&pi   //	接收新进程的识别信息的PROCESS_INFORMATION结构体。进程线程以及句柄
			))
	{
		handle_of_process[n_clone] = pi.hProcess;
		handle_of_thread[n_clone] = pi.hThread;
		return;
	}
	else
	{
		printf("CreateProcess failed (%d).\n", GetLastError());
		return;
	}
}

void consume(int n_clone)
{
	DWORD pid = GetCurrentProcessId(); //当前进程id
	open_mux();
	attach_shm();

	for (int i = 0; i < 4; i++)
	{
		srand((unsigned int)time(NULL) + n_clone);
		Sleep((rand() % 4 + 1) * 1000);

		int read_data = read_resource();

		P(print_mux);
		time_t start_process_time = time(NULL);
		printf("consumer  #%6d, read  data %3d at %s", pid, read_data, ctime(&start_process_time));
		V(print_mux);
	}

	detach_shm();
	close_mux();
}

void product(int n_clone)
{
	DWORD pid = GetCurrentProcessId(); //当前进程id
	open_mux();
	attach_shm();

	for (int i = 0; i < 6; i++)
	{
		srand((unsigned int)time(NULL) + n_clone);
		Sleep((rand() % 2 + 1) * 1000);

		int write_data = write_resource();

		P(print_mux);
		time_t start_process_time = time(NULL);
		printf("productor #%6d, write data %3d at %s", pid, write_data, ctime(&start_process_time));
		V(print_mux);
	}

	detach_shm();
	close_mux();
}

void attach_shm()
{
	h_map = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, shared_mem_name.c_str());
	shm = (RESOURCE *)MapViewOfFile(
		h_map,
		FILE_MAP_ALL_ACCESS,
		0,
		0,
		0);
}

void detach_shm()
{
	//解除映射
	UnmapViewOfFile(shm);
}

void create_mux()
{
	empty_mux = CreateSemaphore(NULL, NUM_OF_RESOURCE, NUM_OF_RESOURCE, empty_mux_name.c_str());
	filled_mux = CreateSemaphore(NULL, 0, NUM_OF_RESOURCE, filled_mux_name.c_str());
	rw_mux = CreateSemaphore(NULL, 1, 1, rw_mux_name.c_str());
	print_mux = CreateSemaphore(NULL, 1, 1, print_mux_name.c_str());
}

void open_mux()
{
	empty_mux = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, empty_mux_name.c_str());
	filled_mux = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, filled_mux_name.c_str());
	rw_mux = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, rw_mux_name.c_str());
	print_mux = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, print_mux_name.c_str());
}

void close_mux()
{
	CloseHandle(empty_mux);
	CloseHandle(filled_mux);
	CloseHandle(rw_mux);
	CloseHandle(print_mux);
}

int write_resource()
{
	srand((unsigned int)time(NULL));
	int result = rand() % 100 + 11;
	P(empty_mux);
	P(rw_mux);
	shm->resource[shm->end_ptr] = result;
	shm->end_ptr++;
	if (shm->end_ptr >= NUM_OF_RESOURCE)
	{
		shm->end_ptr = 0;
	}
	V(rw_mux);
	V(filled_mux);
	return result;
}

int read_resource()
{
	int result = 0;
	P(filled_mux);
	P(rw_mux);
	result = shm->resource[shm->from_ptr];
	shm->from_ptr++;
	if (shm->from_ptr >= NUM_OF_RESOURCE)
	{
		shm->from_ptr = 0;
	}

	V(rw_mux);
	V(empty_mux);
	return result;
}

void P(HANDLE &mutex)
{
	WaitForSingleObject(mutex, INFINITE);
}

void V(HANDLE &mutex)
{
	ReleaseSemaphore(mutex, 1, NULL);
}

运行输出

1575637575292

参考资料

Linux

Windows

posted @ 2019-12-06 21:12  LightningStar  阅读(3680)  评论(0编辑  收藏  举报