线程实现令牌桶(封装成库函数)
需求分析:
我们都知道,缓冲区这个东西是用来暂时存放数据的,等到特定条件比如说缓冲区满
那么系统就会把缓冲区真正写入,所以我们可以发现,计算机里的数据不是像小河流水一样
匀速的涓涓细流,而是有时候没有,有时候一大堆
那么怎么办呢?
很简单呗,大学生不就是有钱的时候猛花猛花,没钱的时候就省着用
所以啊,我们可以安排一个存储机制,平时积攒积蓄,等到要用的时候,我们也有应对的方法
这其实就是令牌桶的思想,当然只要我有在攒,那么我们也可以实现小河流水,也可以应对惊涛骇浪
这里就可以看出, 令牌桶的整体思想其实和生产者消费者模型类似
前置知识:
pthread_once()
参数:pthread_once_t类型的变量的地址和一个函数指针
函数指针指向的函数要求:返回值为void参数为void
功能:函数指针指向的函数只被执行一次
返回值:
令牌桶,我们希望交由用户指定每秒产生令牌数量和令牌桶容量大小
所以我们用一个数组来挂载桶,类似一个桶数组(其实是二维数组)
所以有三个文件,主函数文件,令牌桶头文件,令牌桶实现文件
语言是c语言(感觉c++会好一些,不过没差)
实现效果,类似linux命令下的cat显示文件内容
业务:实现效果,类似linux命令下的cat显示文件内容
限流算法:令牌桶
技术:多线程,互斥量
主函数:main.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include "mytbf.h"
#define CPS 10 //每秒令牌生成数量
#define BUFSIZE 1024 //缓冲区大小
#define BURST 100 //桶的大小
static volatile int token = 0;
int main(int argc, char **argv) //第二个参数是要查看的文件
{
int sfd, dfd = 1; //sfd是要读取的文件,dfd是要输出的文件,切记,Linux一切皆文件,所以最后其实是输出到1
char buf[BUFSIZE]; //缓冲区
int len, pos; //len是要处理的业务的长度,pos是在缓冲区的索引
mytbf_t *tbf; //待会指向用户创建的令牌桶
int size; //实际从文件里读取的数量
int ret; //往1里写入的数量
if (argc < 2)
{
fprintf(stderr, "Usage...\nn");
exit(1);
}
tbf = mytbf_init(CPS, BURST); //初始化令牌桶
if (tbf == NULL)
{
fprintf(stderr, "mytbf_init() failed!\n");
exit(1);
}
do
{
sfd = open(argv[1], O_RDONLY); //打开指定的文件,以只读模式
if (sfd < 0)
{
if (errno != EINTR)
{
perror("open(0)");
exit(1);
}
}
}while (sfd < 0);
while (1)
{
size = mytbf_fetchtoken(tbf, BUFSIZE); //从桶里拿令牌,尽可能多的拿,也就是拿缓冲区大小个,返回实际拿到的数量
if (size < 0)
{
//拿失败的话
fprintf(stderr, "mytbf_fetchtoken():%s\n", strerror(-size));
exit(1);
}
while ((len = read(sfd, buf, size)) < 0) //有了令牌以后就可以处理业务了,也就是读文件,len是实际读的数量
{
//读失败的话
if (errno == EINTR) continue;
perror("read()");
break;
}
if (len == 0) //如果已经全部读完
break;
if (size - len > 0) mytbf_returntoken(tbf, size - len); //如果拿到的令牌多余读取的数量,说明没有必要用那么多,就还回去一些
pos = 0; //从索引0开始写入文件中
while (len > 0)
{
ret = write(dfd, buf + pos, len); //写入文件
if (ret < 0)
{
if (errno == EINTR) continue;
perror("write()");
exit(1);
}
pos += ret;
len -= ret;
}
}
close(sfd); //关闭文件
mytbf_destroy(tbf); //销毁令牌桶
exit(0);
}
makefile文件:
CFLAGS+=-pthread
LDFLAGS+=-pthread
all:mytbf
mytbf:main.o mytbf.o
gcc $^ -o $@ $(CFLAGS) $(LDFLAGS)
clean:
rm -rf *.o mytbf
头文件mytbf.h
//条件编译
#ifndef MYTBF_H__
#define MYTBF_H__
#define MYTBF_MAX 1024
typedef void mytbf_t; //用void 来当作自定义类型,因为任何类型传给void和void传给任何类型都是合理的
mytbf_t *mytbf_init(int cps, int burst);
int mytbf_fetchtoken(mytbf_t *, int );
int mytbf_returentoken(mytbf_t *, int );
int mytbf_destroy(mytbf_t *);
#endif
实现文件:mytbf.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include "mytbf.h"
static struct mytbf_st* job[MYTBF_MAX]; //令牌桶数组,每个元素都是一个令牌桶
static pthread_mutex_t mut_job = PTHREAD_MUTEX_INITIALIZER; //全局互斥量并且静态初始化
static pthread_t tid_alrm; //线程标识
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
static int inited = 0;
//封装成结构体
struct mytbf_st
{
int cps; //每秒令牌生成数量
int burst; //桶的大小
int token; //桶里的令牌数量
int pos; //在令牌桶里的索引
pthread_mutex_t mut; //每个令牌桶都有属于自己的互斥量,全局互斥量用与令牌桶数组
};
//挂载令牌桶
static void *thr_alrm(void *p)
{
while (1)
{
pthread_mutex_lock(&mut_job);
for (int i = 0; i < MYTBF_MAX; i ++ )
{
if (job[i] != NULL)
{
pthread_mutex_lock(&job[i]->mut); //由于要对某个令牌桶进行写操作,所以要确保是独占,所以锁上
job[i]->token += job[i]->cps;
if(job[i]->token > job[i]->burst) //如果桶里的令牌多余上限,那么最多也就只有上限
job[i]->token = job[i]->burst;
pthread_mutex_unlock(&job[i]->mut); //写操作已经执行完,没必要再锁住
}
}
pthread_mutex_unlock(&mut_job); //写操作已经执行完,没必要再锁住
sleep(1);
}
}
//删除令牌桶数组
static void module_unload(void)
{
int i;
pthread_cancel(tid_alrm);
pthread_join(tid_alrm, NULL);
for (i = 0; i < MYTBF_MAX; i ++ )
{
if (job[i] != NULL)
{
mytbf_destroy(job[i]);
}
}
pthread_mutex_destroy(&mut_job);
}
static void module_load(void)
{
int err;
err = pthread_create(&tid_alrm, NULL, thr_alrm, NULL);
if (err)
{
fprintf(stderr, "pthread_create():%s\n", strerror(err));
exit(1);
}
atexit(module_unload);
}
//在令牌桶数组上找一个空余的位置
static int get_free_pos_unlocked(void)
{
int i;
for (i = 0; i < MYTBF_MAX; i ++ )
{
if (job[i] == NULL)
return i;
}
return -1;
}
//令牌桶初始化
mytbf_t *mytbf_init(int cps, int burst)
{
struct mytbf_st *me;
int pos;
pthread_once(&init_once, module_load);
me = malloc(sizeof (*me));
if (me == NULL)
return NULL;
me->token = 0;
me->cps = cps;
me->burst = burst;
pthread_mutex_init(&me->mut, NULL);
pthread_cond_init(&me->cond, NULL);
pthread_mutex_lock(&mut_job); //全局互斥量是对job数组进行限制,所以当需要操作job数组时,就上锁
pos = get_free_pos_unlocked(); //get_free_pos_unlocked函数内只是对job数组进行读取,所以没必要上锁,但是
//用户可能用带着锁的线程执行这个函数,所以加上unlocked提示用户
if (pos < 0)
{
pthread_mutex_unlock(&mut_job);
free(me);
return NULL;
}
me->pos = pos;
pthread_mutex_unlock(&mut_job);
return me;
}
//获取令牌,获取size个
int mytbf_fetchtoken(mytbf_t *ptr, int size)
{
struct mytbf_st *me = ptr;
int n;
if (size <= 0)
return -EINVAL;
pthread_mutex_lock(&me->mut);
while (me->token <= 0) //当令牌桶里没有令牌时,就松开一会
{
pthread_mutex_unlock(&me->mut);
sched_yield();
pthread_mutex_lock(&me->mut);
}
n = min(me->token, size); //防止过多的拿
me->token -= n;
pthread_mutex_unlock(&me->mut);
return n;
}
//用户的需求小于给用户的令牌时,用户要归还令牌
int mytbf_returntoken(mytbf_t *ptr, int size)
{
struct mytbf_st *me = ptr;
if (size <= 0)
{
return -EINVAL;
}
pthread_mutex_lock(&me->mut);
me->token += size;
if (me->token > me->burst)
{
me->token = me->burst;
}
pthread_mutex_unlock(&me->mut);
return size;
}
int mytbf_destroy(mytbf_t *ptr)
{
struct mytbf_st *me = ptr;
pthread_mutex_lock(&mut_job);
job[me->pos] = NULL;
pthread_mutex_unlock(&mut_job);
pthread_mutex_destroy(&me->mut);
free(ptr);
return 0;
}
最后,要实现的功能只是酒,重点在于装酒的瓶子,也就是令牌桶算法+多线程编程+互斥量的使用理解