令牌桶算法——多线程实现

相关注意事项都写在了注释当中
【互斥锁版本】
注意,若不满足到对某资源进行操作的条件,则一直while循环,反复轮询“开锁——等待——加锁”的过程,会造成“忙等”的现象。解决方法是通过条件变量的方式。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>

#include "mytbf.h"
// typedef void (*sighandler_t)(int);

struct mytbf_st
{
    int cps;    // 每秒传输
    int burst;  // 最大限制
    int token;  // 积累传输量
    int pos;    // 记录当前令牌桶在令牌桶数组中的下标
    pthread_mutex_t mut;
};

struct mytbf_st *job[MYTBF_MAX];
static pthread_t tid_alrm;
static pthread_once_t ini_once = PTHREAD_ONCE_INIT;
static pthread_mutex_t mut_job = PTHREAD_MUTEX_INITIALIZER;

// 命名中应该体现,此函数需要加锁才能使用 xxx_unlocked()
static int get_free_pos_unlocked(void)
{
    for(int i = 0; i < MYTBF_MAX; i++)
    {
        if(job[i] == NULL)
            return i;
    }
    return -1;
}

static void module_unload(void)
{
    pthread_cancel(tid_alrm);
    pthread_join(tid_alrm, NULL);
    for(int i = 0; i < MYTBF_MAX; i++)
    {
        if(job[i] != NULL)
        {
            mytbf_destroy(job[i]);
        }
    }
    pthread_mutex_destroy(&mut_job);
}

static void *thr_alrm(void *p)
{
    while(1)
    {
        for(int i = 0; i < MYTBF_MAX; i++) {
            pthread_mutex_lock(&mut_job);
            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);
    }

    // pthread_exit(NULL);
}

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(errno));
        pthread_join(tid_alrm, NULL);
        exit(1);
    }

    // alrm_handler_save = signal(SIGALRM, alrm_handler);
    // alarm(1);   
    // setitimer(ITIMER_REAL, itm, NULL);
    
    // 因为thr_alrm线程一直在后台运行(while(1)),而pthread_join的作用是阻塞等待,故程序会一直卡在pthread_join处
    // 而不向后执行,因此这里不能使用pthread_join,而是在module_unload函数中使用cancel取消线程,接着join
    // pthread_join(tid_alrm, NULL);
    atexit(module_unload);
}

mytbf_t *mytbf_init(int cps, int burst)
{
    int pos;
    struct mytbf_st *me;
    // 在init函数中使用alarm来定时,同时也要考虑到一个程序中可能创建不止一个令牌桶,如果第二个令牌桶也
    // 调用init,那么会出现两次alarm,程序也会因此执行出错。故应该将alarm设置为只在首次调用时使用。
    pthread_once(&ini_once, module_load);

    me = (struct mytbf_st *)malloc(sizeof(struct mytbf_st));
    if(me == NULL) {
        return NULL;
    }
    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    pthread_mutex_init(&me->mut, NULL);

    // 临界区内的跳转语句一定要记住归还锁的状态
    pthread_mutex_lock(&mut_job);
    pos = get_free_pos_unlocked();
    if(pos == -1) {
        pthread_mutex_unlock(&mut_job);
        free(me);
        return NULL;
    }

    me->pos = pos;

    job[pos] = me;
    pthread_mutex_unlock(&mut_job);
    return me;
}

static int min(const int a, const int b)
{
    return a < b ? a : b;
}

int mytbf_fetchtoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me = ptr;
    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);
    }

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

    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(me);
    return 0;
}

【条件变量版本】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>

#include "mytbf.h"
// typedef void (*sighandler_t)(int);

struct mytbf_st
{
    int cps;               // 每秒传输
    int burst;             // 最大限制
    int token;             // 积累传输量
    int pos;               // 记录当前令牌桶在令牌桶数组中的下标
    pthread_mutex_t mut;   // token互斥量
    pthread_cond_t cond;   // token条件变量
};

struct mytbf_st *job[MYTBF_MAX];
static pthread_t tid_alrm;
static pthread_once_t ini_once = PTHREAD_ONCE_INIT;
static pthread_mutex_t mut_job = PTHREAD_MUTEX_INITIALIZER;

// 命名中应该体现,此函数需要加锁才能使用 xxx_unlocked()
static int get_free_pos_unlocked(void)
{
    for(int i = 0; i < MYTBF_MAX; i++)
    {
        if(job[i] == NULL)
            return i;
    }
    return -1;
}

static void module_unload(void)
{
    pthread_cancel(tid_alrm);
    pthread_join(tid_alrm, NULL);
    for(int i = 0; i < MYTBF_MAX; i++)
    {
        if(job[i] != NULL)
        {
            mytbf_destroy(job[i]);
        }
    }
    pthread_mutex_destroy(&mut_job);
}

static void *thr_alrm(void *p)
{
    while(1)
    {
        for(int i = 0; i < MYTBF_MAX; i++) {
            pthread_mutex_lock(&mut_job);
            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;
                
                // 十个令牌桶都在等token,如果使用signal,则只是唤醒其中一个(具体哪个不知道)
                // 假设当前刚给0号令牌桶加完token,使用signal可能唤醒的是第7号令牌桶,显然不对。因此要broadcast广播
                // broadcast不可避免的引起了惊群效应
                pthread_cond_broadcast(&job[i]->cond);
                pthread_mutex_unlock(&job[i]->mut);
            }
            pthread_mutex_unlock(&mut_job);
        }
        sleep(1);
    }

    // pthread_exit(NULL);
}

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(errno));
        pthread_join(tid_alrm, NULL);
        exit(1);
    }
    
    // 因为thr_alrm线程一直在后台运行(while(1)),而pthread_join的作用是阻塞等待,故程序会一直卡在pthread_join处
    // 而不向后执行,因此这里不能使用pthread_join,而是在module_unload函数中使用cancel取消线程,接着join
    // pthread_join(tid_alrm, NULL);
    atexit(module_unload);
}

mytbf_t *mytbf_init(int cps, int burst)
{
    int pos;
    struct mytbf_st *me;
    // 在init函数中使用alarm来定时,同时也要考虑到一个程序中可能创建不止一个令牌桶,如果第二个令牌桶也
    // 调用init,那么会出现两次alarm,程序也会因此执行出错。故应该将alarm设置为只在首次调用时使用。
    pthread_once(&ini_once, module_load);

    me = (struct mytbf_st *)malloc(sizeof(struct mytbf_st));
    if(me == NULL) {
        return NULL;
    }
    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    pthread_mutex_init(&me->mut, NULL);
    pthread_cond_init(&me->cond, NULL);

    // 临界区内的跳转语句一定要记住归还锁的状态
    pthread_mutex_lock(&mut_job);
    pos = get_free_pos_unlocked();
    if(pos == -1) {
        pthread_mutex_unlock(&mut_job);
        free(me);
        return NULL;
    }

    me->pos = pos;

    job[pos] = me;
    pthread_mutex_unlock(&mut_job);
    return me;
}

static int min(const int a, const int b)
{
    return a < b ? a : b;
}

int mytbf_fetchtoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me = ptr;
    if(size <= 0)
        return -EINVAL;
    
    // 取令牌时,若桶中为空,则等待桶中有令牌再取
    pthread_mutex_lock(&me->mut);
    while(me->token <= 0)
    {
        // 发现条件成立,解锁等待,直到收到broadcast或者signal的通知,然后抢(加)锁。若未抢到则继续阻塞。
        // 条件变量对应通知机制,对比于查询机制,可以不用忙等,而是阻塞等待
        pthread_cond_wait(&me->cond, &me->mut);
        // pthread_mutex_unlock(&me->mut);
        // sched_yield();
        // pthread_mutex_lock(&me->mut);
    }

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

    if(size <= 0) {
        return -EINVAL;
    }

    pthread_mutex_lock(&me->mut);
    me->token += size;

    // 令牌桶满则丢弃溢出的部分
    if(me->token > me->burst) {
        me->token = me->burst;
    }
    pthread_cond_broadcast(&me->cond);
    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);
    pthread_cond_destroy(&me->cond);
    free(me);
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
令牌算法是一种流量控制算法,可以用于限制某个接口或服务的访问频率。下面是一种简单的Java实现: ```java import java.util.concurrent.atomic.AtomicLong; public class TokenBucket { private final AtomicLong tokens; // 当前可用的令牌数 private final int capacity; // 令牌桶的容量 private final long refillInterval; // 令牌填充的时间间隔 private final long refillAmount; // 每次填充的令牌数 private volatile long lastRefillTime; // 上次填充令牌的时间 public TokenBucket(int capacity, long refillInterval, long refillAmount) { tokens = new AtomicLong(capacity); this.capacity = capacity; this.refillInterval = refillInterval; this.refillAmount = refillAmount; lastRefillTime = System.currentTimeMillis(); } // 尝试获取令牌,成功返回true,失败返回false public boolean tryAcquire() { refill(); return tokens.getAndUpdate(n -> n > 0 ? n - 1 : 0) > 0; } // 填充令牌 private void refill() { long now = System.currentTimeMillis(); long elapsedTime = now - lastRefillTime; if (elapsedTime > refillInterval) { long newTokens = elapsedTime / refillInterval * refillAmount; lastRefillTime += elapsedTime / refillInterval * refillInterval; tokens.updateAndGet(n -> Math.min(capacity, n + newTokens)); } } } ``` 在上述实现中,TokenBucket类的构造函数需要传入令牌桶的容量、令牌填充的时间间隔以及每次填充的令牌数。tryAcquire方法用于尝试获取一个令牌,如果当前可用令牌数大于0,则将可用令牌数减1并返回true,否则直接返回false。refill方法用于填充令牌,如果当前时间距离上次填充令牌的时间超过了填充间隔,则计算应该填充的令牌数,并更新可用令牌数。注意,在更新可用令牌数时,需要使用AtomicLong类来保证线程安全。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值