Linux 多线程/进程同步

Linux 多线程/进程同步

最近写作业学习了多线程/进程的经典同步问题,即生产者-消费者,在此做一些记录。主要是使用了semaphore.h中的POSIX信号量。

多线程

原理

多线程模拟一对一生产者-消费者,核心是三个:生产线程、消费线程、缓冲区。生产线程和消费线程同时执行,不考虑相互的直接影响,而是仅通过缓冲区的满和空控制生产阻塞和消费阻塞。生产线程向缓冲区放置产品,并减小其剩余容量,当剩余容量为0时生产暂停/阻塞,消费线程从缓冲区提取产品,并增大其剩余容量,当库存为0时消费暂停/阻塞。

方案

多线程实现生产者-消费者模型,要实现几个方面:

  1. 产品缓冲区,为了比较好的模拟实际情况,我使用了产品结构体指针循环队列,生产者使用calloc生产一个商品,使用push将其放入队列尾部,消费者使用pop从队列首部获得产品结构体指针,显示其gid,并使用free将其消耗(同时防止内存泄漏)。
  2. 控制生产阻塞和消费阻塞,使用两个sem_t信号量实现,其中g_sem_full控制生产阻塞,初始值为缓冲区容量,生产者生产之前对其sem_wait,消费者消费之后对其sem_post,g_sem_empty控制消费阻塞,初始值为0,消费者消费之前对其sem_wait,生产者生产之后对其sem_post。
  3. 三是实现对队列操作的互斥,使用pthread互斥锁pthread_mutex_t实现,在队列操作之前请求上锁,在完成操作之后解锁。
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define QUEUE_SIZE 4
#define COUNT 20

struct goods {
	int gid;
};

struct goods *queue[QUEUE_SIZE];
int head = 0;
int rear = 0;
int count = 0; // for using the full-size queue
int g_pcount = 0;
int g_ccount = 0;
pthread_mutex_t g_mutex;
sem_t g_sem_full;
sem_t g_sem_empty;

bool empty();
bool full();
int push(struct goods *);
struct goods *pop();
void *provider(void *);
void *consumer(void *);

int main()
{
	srand(getpid());
	sem_init(&g_sem_full, 0, QUEUE_SIZE);
	sem_init(&g_sem_empty, 0, 0);
	pthread_mutex_init(&g_mutex, NULL);
	pthread_t p1;
	pthread_t c1;
	pthread_create(&p1, NULL, provider, NULL);
	pthread_create(&c1, NULL, consumer, NULL);
	pthread_join(p1, NULL);
	pthread_join(c1, NULL);
	pthread_mutex_destroy(&g_mutex);
	sem_destroy(&g_sem_full);
	sem_destroy(&g_sem_empty);
}

bool empty()
{
	if (count == 0)
		return true;
	return false;
}

bool full()
{
	if (count == QUEUE_SIZE)
		return true;
	return false;
}

int push(struct goods *item)
{
	if (full())
		return -1;
	queue[rear] = item;
	rear = (rear+1) % QUEUE_SIZE;
	count++;
	return rear;
}

struct goods *pop()
{
	if (empty())
		return NULL;
	int temp = head;
	head = (head+1) % QUEUE_SIZE;
	count--;
	return queue[temp];
}

void *provider(void *args)
{
	struct goods *g;
	printf("I'm provider %u\n", (unsigned int)syscall(224)); // print kernel thread id
	sleep(1);
	while (g_pcount < COUNT) {
		sem_wait(&g_sem_full);
		pthread_mutex_lock(&g_mutex);
		g = (struct goods *)calloc(1, sizeof(struct goods));
		g->gid = rand();
		printf("produce %d", g->gid);
		push(g);
		if (full())
			printf(" now full\n");
		else
			printf("\n");
		sem_post(&g_sem_empty);
		pthread_mutex_unlock(&g_mutex);
		sleep(rand() % 4); // production is faster
		g_pcount++;
	}
}

void *consumer(void *args)
{
	printf("I'm consumer %u\n", (unsigned int)syscall(224));
	sleep(1);
	while (g_ccount < COUNT) {
		sem_wait(&g_sem_empty);
		pthread_mutex_lock(&g_mutex);
		struct goods *g = pop();
		printf("consume %d", g->gid);
		free(g);
		if (empty())
			printf(" now empty\n");
		else
			printf("\n");
		sem_post(&g_sem_full);
		pthread_mutex_unlock(&g_mutex);
		sleep(rand() % 6);
		g_ccount++;
	}
}

结果

I'm consumer 6891
I'm provider 6890
produce 780258551
consume 780258551 now empty
produce 1519689668
consume 1519689668 now empty
produce 112245485
produce 85184545
produce 1522912016
consume 112245485
consume 85184545
produce 552356389
produce 1496202163
produce 1047747442 now full
consume 1522912016
produce 122094059 now full
consume 552356389
produce 405903396 now full
consume 1496202163
consume 1047747442
consume 122094059
consume 405903396 now empty

可以看到出现now empty之后必须先出现produce才能继续出现consume,反之now full亦然。

多进程

原理

多进程间的生产者-消费者和多线程的有所不同,因为进程间并不共享栈和堆空间,像在目标2所写的缓冲区将不会起作用,因为缓冲区保存的是产品结构体的指针,即使两个进程间将缓冲区共享内存,其中的指针所指的对象仍然只能在一个进程有效(一开始在这个方面找了好久的bug,消费者进程获取到的产品gid一直为0)。所以最重要的一点是将缓冲区修改为保存产品的全部信息。另外一点,则是信号量的共享问题,可以利用mmap函数将一个长度为sem_t大小的文件映射在内存中,将信号量放在映射区中,便可以让信号量在多进程间起作用。

方案

对实验目标2中的方案进行一些修改:

  1. 缓冲区不再保存产品结构体的指针,而是直接保存完整结构体,并将队列的一些相关变量比如head, rear封装,本来应该也将队列操作的函数一起封装,可是不像C++,C语言中结构体中只能加入函数指针,于是放弃。push函数使用memcpy拷贝完整对象,所以在生产者中,push之后要将产品指针进行free,因为我们只需要在缓冲区中的那一份,并且防止内存泄漏。
  2. 使用mmap将信号量g_sem_full, g_sem_empty, g_sem_lock以共享内存的方式在进程间起作用。
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define QUEUE_SIZE 4
#define COUNT 10

struct goods {
	int gid;
};

struct que {
	int head;
	int rear;
	int count;
	struct goods data[QUEUE_SIZE];
} *queue;

int g_pcount = 0;
int g_ccount = 0;
sem_t *g_sem_full;
sem_t *g_sem_empty;
sem_t *g_sem_lock;

void init();
bool empty();
bool full();
int push(struct goods *);
struct goods *pop();
void *provider();
void *consumer();

int main()
{
	pid_t pid;
	srand(getpid());
	init();
	sem_init(g_sem_full, 1, QUEUE_SIZE); // set the second param to 1 to share among processes
	sem_init(g_sem_empty, 1, 0);
	sem_init(g_sem_lock, 1, 1);
	pid = fork();
	if (pid > 0) {
		(*provider)();
		wait(NULL); // wait for child process
	}
	else if (pid == 0) {
		(*consumer)();
	}
	else
		perror("fork");
	sem_destroy(g_sem_full);
	sem_destroy(g_sem_empty);
	sem_destroy(g_sem_lock);
	munmap(g_sem_full, sizeof(sem_t));
	munmap(g_sem_empty, sizeof(sem_t));
	munmap(g_sem_lock, sizeof(sem_t));
	munmap(queue, sizeof(struct que));
}

void init()
{
	int fd_full, fd_empty, fd_data, fd_lock;
	if ((fd_full = open("sem_full", O_RDWR | O_CREAT | O_TRUNC, 0666)) == -1) {
		perror("open fd_full");
		exit(-1);
	}
	if ((fd_empty = open("sem_empty", O_RDWR | O_CREAT | O_TRUNC, 0666)) == -1) {
		perror("open fd_empty");
		exit(-1);
	}
	if ((fd_data = open("data", O_RDWR | O_CREAT | O_TRUNC, 0666)) == -1) {
		perror("open data");
		exit(-1);
	}
	if ((fd_lock = open("sem_lock", O_RDWR | O_CREAT | O_TRUNC, 0666)) == -1) {
		perror("open fd_lock");
		exit(-1);
	}

	ftruncate(fd_full, sizeof(sem_t));
	ftruncate(fd_empty, sizeof(sem_t));
	ftruncate(fd_lock, sizeof(sem_t));
	ftruncate(fd_data, sizeof(struct que));
	if ((g_sem_full = (sem_t *)mmap(0, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd_full, 0)) == MAP_FAILED) {
		perror("mmap sem_full");
		exit(-1);
	}
	if ((g_sem_empty = (sem_t *)mmap(0, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd_empty, 0)) == MAP_FAILED) {
		perror("mmap sem_empty");
		exit(-1);
	}
	if ((g_sem_lock = (sem_t *)mmap(0, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd_lock, 0)) == MAP_FAILED) {
		perror("mmap sem_lock");
		exit(-1);
	}
	if ((queue = (struct que *)mmap(0, sizeof(struct que), PROT_READ | PROT_WRITE, MAP_SHARED, fd_data, 0)) == MAP_FAILED) {
		perror("mmap queue");
		exit(-1);
	}
	queue->head = queue->rear = queue->count = 0;

	close(fd_full);
	close(fd_empty);
	close(fd_lock);
	close(fd_data);
}

bool empty()
{
	if (queue->count == 0)
		return true;
	return false;
}

bool full()
{
	if (queue->count == QUEUE_SIZE)
		return true;
	return false;
}

int push(struct goods *item)
{
	if (full())
		return -1;
	memcpy(&queue->data[queue->rear], item, sizeof(struct goods)); // copy the full struct
	queue->rear = (queue->rear+1) % QUEUE_SIZE;
	queue->count++;
	return queue->rear;
}

struct goods *pop()
{
	if (empty())
		return NULL;
	int temp = queue->head;
	queue->head = (queue->head+1) % QUEUE_SIZE;
	queue->count--;
	return queue->data+temp;
}

void *provider()
{
	struct goods *g;
	printf("I'm provider %d\n", getpid());
	sleep(1);
	while (g_pcount < COUNT) {
		sem_wait(g_sem_full);
		sem_wait(g_sem_lock);
		g = (struct goods *)calloc(1, sizeof(struct goods));
		g->gid = rand();
		printf("produce %d", g->gid);
		push(g);
		free(g); // prevent memory leak
		if (full())
			printf(" now full\n");
		else
			printf("\n");
		sem_post(g_sem_lock);
		sem_post(g_sem_empty);
		sleep(rand() % 4);
		g_pcount++;
	}
}

void *consumer()
{
	printf("I'm consumer %d\n", getpid());
	sleep(1);
	while (g_ccount < COUNT) {
		sem_wait(g_sem_empty);
		sem_wait(g_sem_lock);
		struct goods *g = pop();
		printf("consume %d", g->gid);
		if (empty())
			printf(" now empty\n");
		else
			printf("\n");
		sem_post(g_sem_lock);
		sem_post(g_sem_full);
		sleep(rand() % 6);
		g_ccount++;
	}
}

结果

I'm provider 17556
I'm consumer 17557
produce 422607159
produce 1647533622
consume 422607159
produce 1691926058
consume 1647533622
consume 1691926058 now empty
produce 1198674947
consume 1198674947 now empty
produce 1643959313
consume 1643959313 now empty
produce 463976074
produce 2118660340
consume 463976074
produce 1925818684
produce 1318558585
produce 1580507178 now full
consume 2118660340
consume 1925818684
consume 1318558585
consume 1580507178 now empty

可以看到输出的结果和目标2中是类似的。

其他

在这些程序中都是使用sem_t这种POSIX信号量,另外还有一种System V的信号量。

Linux进程同步之System V 信号量

System V POSIX
semctl() sem_getvalue()
semget() sem_post()
semop() sem_timedwait()
sem_trywait()
sem_wait()
sem_destroy()
sem_init()
sem_close()
sem_open()
sem_unlink()

你可能感兴趣的:(c)