探究一种定长队列操作(C语言版本)

一 问题来源:南京烽火通信面试

二 问题:

现有一种定长队列,长度为2的n次方,此队列可被多线程访问,但必须确保线程级安全,即在任意时刻,队列的长度保持不变。

三 笔者分析

1. 队列的存储结构--链式和顺序均可。

2. 长度为2的n次方--便于移动队头和队尾指针,假设队列长度为size,那么rear = (rear + 1) & (size - 1),&运算比算术求模运算效率高。为此,笔者使用了顺序队列。

3. 怎么确保线程级安全

笔者认为此系统一定同时存在多个读者和写者,读者是读取队头元素,写者是不断向队尾插入元素,读者与读者之间,读者与写者之间,写者与写者之间都必须互斥访问,因此,必须采用互斥锁,保证任意时刻队列长度不变。为此,笔者定义一个全局变量balance。

读者 get

加锁

检测balance为1吗?如果为1,表明队尾已经有一个元素加入, 那么读取队头,并将balance = 0

解锁

写者 put

加锁

检测balance为0吗?如果为0,表明队列平衡,没有元素插入, 那么在队列插入元素,并将balance = 1

解锁

这样能够确保同一时刻读、写者之间彼此是互斥的。

四 代码组织

五 代码详解

1. queue.h

/*************************************************************************
    > File Name: queue.h
    > Author: wangzhicheng
    > Mail: [email protected]
    > Created Time: Fri 14 Nov 2014 11:23:55 AM WST
 ************************************************************************/
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define MAX 1024    // the max storage space
typedef int DataType;   // data type
/*
 * sequenece queue
 * */
typedef struct Queue {
	DataType *array;
	int front;       // pointer to the header node of queue
	int rear;        // pointer to the next position of last node in the queue
	int size;        // initial size of the queue size should be 8 bytes
}Queue;
Queue Q;
int balance;   // should be 0, 1 -- a node add in the queue
pthread_mutex_t mutex;  // mutex lock
int InitQueue();
int reInitQueue(uint);
int get(DataType *);
int put(DataType);
#endif

2. queue.c

/*************************************************************************
    > File Name: queue.c
    > Author: wangzhicheng2013
    > Mail: [email protected]
    > Created Time: Fri 14 Nov 2014 03:01:32 PM WST
 ************************************************************************/
#include "queue.h"
/*
 * initialize the queue
 * return 0 if successfully
 * else return -1
 * */
int InitQueue() {
	int rc = 0;
	int i;
	Q.array = (DataType *)malloc(sizeof(DataType) * MAX);
	if(!Q.array) {
		rc = -1;
		printf("Init Queue failed, Memeory lack...!\n");
	}
	else {  // initialize the queue
		Q.front = 0;
		Q.size = 8;
		Q.rear = Q.size;
		for(i = 0;i < Q.size;i++) Q.array[i] = i;
		pthread_mutex_init(&mutex, NULL);
	}

	return rc;
}
/*
 * reinitialize the queue
 * return 0 if successfully
 * else return -1
 * */
int reInitQueue(uint capacity) {
	int rc = 0;
	int i;
	if(capacity >= MAX) rc = -1;
	else {  // initialize the queue
		for(i = 2;i < capacity;i = i << 1) ;
		if(i > capacity) i = i / 2;  // compute the max 2 ^ n less than capacity
		Q.size = i;
		for(i = Q.rear;i < Q.size;i++) Q.array[i] = i;
		Q.rear = Q.size;
	}

	return rc;
}
/*
 * get a element from the queue
 * return 0 if successfully
 * else return -1
 * */
int get(DataType *e) {
	int rc = -1;
	pthread_mutex_lock(&mutex);
	if(balance) {   // if put a node in the queue before
		*e = Q.array[Q.front];
		Q.front = (Q.front + 1) & (Q.size - 1);  // circularly move, save much time
		balance = 0; // return orgin
		rc = 0;
	}
	pthread_mutex_unlock(&mutex);

	return rc;
}
/*
 * put a element in the queue
 * return 0 if successfully
 * else return -1
 * */
int put(DataType e) {
	int rc = -1;
	pthread_mutex_lock(&mutex);
	if(!balance) {   // if the queue is balanced
		Q.array[Q.rear] = e;
		Q.rear = (Q.rear + 1) & (Q.rear - 1);  // circularly move, save much time
		balance = 1;
		rc = 0;
	}
	pthread_mutex_unlock(&mutex);

	return rc;
}

3. main.c

/*************************************************************************
    > File Name: main.c
    > Author: ma6174
    > Mail: [email protected]
    > Created Time: Fri 14 Nov 2014 03:26:48 PM WST
 ************************************************************************/

#include "queue.h"
#include <time.h>
#define THREAD_NUM 20
void *_read(void *arg) {
	DataType e;
	while(1) {
		if(get(&e)) {
			perror("read failed...!\n");
		}
		else printf("%d has been read...!\n", e);
		usleep(20);
//		sleep(1);
	}
}
void *_write(void *arg) {
	DataType e = rand() % 10;
	while(1) {
		if(put(e)) {
			perror("write failed...!\n");
		}
		else printf("%d has been write...!\n", e);
		usleep(20);
//		sleep(1);
	}
}
int main() {
	if(InitQueue()) {
		exit(EXIT_FAILURE);
	}
	srand((unsigned)time(NULL));
	pthread_t threads[THREAD_NUM];
	int i;
	for(i = 0;i < THREAD_NUM;i++) {
		if(i < THREAD_NUM / 2) {
			if(pthread_create(&threads[i], NULL, _read, NULL)) {
				perror("thread created failed...!\n");
			}
		}
		else {
			if(pthread_create(&threads[i], NULL, _write, NULL)) {
				perror("thread created failed...!\n");
			}
		}
	}
	for(i = 0;i < THREAD_NUM;i++) {
		pthread_join(threads[i], NULL);
	}

	return 0;
}

4. Makefile

CC=gcc
all:
	$(CC) -g -o main src/main.c src/queue.c header/queue.h -I src/ -I header/ -lpthread

六 运行测试

时间: 2024-10-12 09:03:58

探究一种定长队列操作(C语言版本)的相关文章

定长串操作

串操作在数据结构中也是十分重要的一部分,首先需要理解串,串长,串相等,空格串,空串,子串的概念.我们在编程过程中,对于字符串的操作,就是一种串结构的使用. 串:是指通常说所的字符串,如:"abcde","PI"等 串长:是指字符串的长度,如:"abcde"长度为5,"PI"长度为2 串相等:两个字符串的长度和内容均相等,如:"abcde"和"abcde" 空格串:字符串由一个或多个空格组

内存池--定长内存池

简介 STL的 __pool_alloc, __mt_alloc,boost的pool系列, ace的ACE_Cached_Allocator均为定长内存池. 说明 内存池,根据存储的元素的长度是否可变,分为变长,与定长两种内存池. 从逻辑上来讲,定长内存池只需存储相同大小的元素,因此无须花费额外的空间(数据结构)来存储元素长度的信息. 以上几种定长内存池都可以较好的处理定长内存频繁分配的问题. STL--pool_alloc pool_alloc,原理是挂了16个链表(_S_free_list

快学Scala 第三课 (定长数组,变长数组, 数组循环, 数组转换, 数组常用操作)

定长数组定义: val ar = new Array[Int](10) val arr = Array("aa", "bb") 定长数组赋值: arr(0) = "cc" 变长数组定义: val ab = new ArrayBuffer[String]() val ab1 = ArrayBuffer[String]() 定长数组增加元素: ab += "aa" ab += ("bb", "cc&q

定长内存池之BOOST::pool

内存池可有效降低动态申请内存的次数,减少与内核态的交互,提升系统性能,减少内存碎片,增加内存空间使用率,避免内存泄漏的可能性,这么多的优点,没有理由不在系统中使用该技术. 内存池分类: 1.              不定长内存池.典型的实现有apr_pool.obstack.优点是不需要为不同的数据类型创建不同的内存池,缺点是造成分配出的内存不能回收到池中.这是由于这种方案以session为粒度,以业务处理的层次性为设计基础. 2.             定长内存池.典型的实现有LOKI.B

(入门篇 NettyNIO开发指南)第四章-分隔符和定长解码器使用

TCP    以流的方式进行数据传输上层的应用协议为了对消息进行区分,往往采用如下4种方式. (1)消息长度固定,累计读取到长度总和为定长LEN 的报文后,就认为读取到了一个完整的消息,将计数器置位,重新开始读取下一个数据报:(2)将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛:(3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符:(4)通过在消息头中定义长度字段来标识消息的总长度. Netty对上面四种应用做了统一的抽象提供了4种解码器来解决

jQuery源码分析系列(38) : 队列操作

Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Queue队列 队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除. 为什么要引入队列? 我们知道代码的执行流有异步与同步之分,例如 var a

定长记录采用数据库读写并非最佳解决方案

对于有些应用场合如仪器仪表的采样数据,不需要对数据排序.插入和修改,只需要对数据写和读操作,在这种情况下,使用数据库来存取这样的记录数据,未必是最佳的选择,本文根据工作实践,采用文件的分块记录的方法,来处理采样这样的定长记录数据,实践证明,通过文件的分块存储方法,比数据库存储方法读写速度更快,尤其是在处理大批量的记录数据的读写的时候,这种速度上的优势更为显著.下面是分块记录的具体实现方法: 首先,假设我们的记录数据为:记录id号,电流,电压,温度,电阻,用结构体表示为: [html] view

JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue

前言:俗话说“金三银四铜五”,不知道我要在这段时间找工作会不会很艰难.不管了,工作三年之后就当给自己放个暑假. 面试当中Collection(集合)是基础重点.我在网上看了几篇讲Collection的文章,大多都是以罗列记忆点的形式书写的,没有谈论实现细节和逻辑原理.作为个人笔记无可厚非,但是并不利于他人学习.希望能通过这种比较“费劲”的讲解,帮助我自己.也帮助读者们更好地学习Java.掌握Java. 无论你跟我一样需要应聘,还是说在校学生学习Java基础,都对入门和进一步启发学习有所帮助.(关

串的定长顺序存储表示

串的定长顺序存储表示是一种类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列. 在实现时主要实现以下功能: int Length(SString s);                               //串长 bool StringConcat(SString T, SString s1, SString s2);//用T返回s1和s2连接的串 void PrintString(char T[]);                          //输出 b