线程实现令牌桶(封装成库函数)

线程实现令牌桶(封装成库函数)

需求分析:
我们都知道,缓冲区这个东西是用来暂时存放数据的,等到特定条件比如说缓冲区满
那么系统就会把缓冲区真正写入,所以我们可以发现,计算机里的数据不是像小河流水一样

匀速的涓涓细流,而是有时候没有,有时候一大堆
那么怎么办呢?

很简单呗,大学生不就是有钱的时候猛花猛花,没钱的时候就省着用
所以啊,我们可以安排一个存储机制,平时积攒积蓄,等到要用的时候,我们也有应对的方法
这其实就是令牌桶的思想,当然只要我有在攒,那么我们也可以实现小河流水,也可以应对惊涛骇浪
这里就可以看出, 令牌桶的整体思想其实和生产者消费者模型类似

前置知识:

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;
}

最后,要实现的功能只是酒,重点在于装酒的瓶子,也就是令牌桶算法+多线程编程+互斥量的使用理解

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值