c语言多线程-模拟微信抢红包

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/stayneckwind2/article/details/54773532

一、背景

想法源于微信、QQ、蓝信抢红包的热情,内部是怎么实现分配处理的呢?
对于单机的情况,是否可以使用多线程去模拟多个用户同时去抢红包?

二、相关知识

大概查找了一下相关的资料[1][2],我理解红包软件实现的主要难点是在存储、分配这两块;存储解决数据原子性的问题、分配则解决先后抢包期望值一致的问题;
2.1 并发操作
用户在微信中抢红包时分成抢包和拆包两个操作。抢包决定红包是否还有剩余金额,但如果行动不够迅速,在拆包阶段可能红包已经被其他用户抢走的情况。”[1];
解决方法是用的CAS去保证并发抢包下的数据原子性(多客户端多个机器的情况下),在本文的多线程模拟中,其实就可以使用线程锁去保证数据的原子性,防止出现1份红包分别被2个人同时拥有;
2.2 金额分配
红包的金额是拆的时候实时计算,而不是预先分配,实时计算基于内存,不需要额外存储空间,并且实时计算效率也很高。每次拆红包时,系统取0.01到剩余平均值*2之间作为红包的金额。"[1];
把红包做成份数去理解会比较清楚一些,红包的当前状态可以表示为 [份数, 金额],如[10,100] 表示当前为10人份总额100元的红包,当客户端拿到 [10,100] 这个状态后,根据seed去获取0.01份~1.99份的当前红包(非最后一份红包的情况);
在实际的微信实现中,这个seed应该就是跟红包id、用户id相关的数据吧,在多线程模拟下,简单起见,直接就用默认的seed就行了;

2.3 相关接口
互斥锁初始化:pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
上锁:pthread_mutex_lock(pthread_mutex_t *mutex);
解锁:pthread_mutex_unlock(pthread_mutex_t *mutex);
条件变量初始化:pthread_cond_init(pthread_cond_t *cond,const pthread_cond_t *attr);
线程挂起等待:pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
唤醒单个:pthread_cond_signal(pthread_cond_t *cond);
全部唤醒:pthread_cond_broadcast(pthread_cond_t *cond);

三、实现

定义部分,instance_t 为程序实例结构体,里面放置线程属性、互斥锁、条件量,
item_t 结构体表示红包条目,成员num为当前红包份数,tot表示当前红包总金额,单位是分;

#define THREAD_NUM 10
#define __RAND(min, max) (rand() % ((max) - (min)) + min)

typedef struct instance
{
    pthread_attr_t attr;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} instance_t;

typedef struct item
{
    int num;
    int tot;
} item_t;

static item_t item = {0};

主线程为生产线程由stdin进行控制发红包,发出红包后通过条件变量广播唤醒所有子线程(10个工作子线程负责抢红包)
int main ()
{
    int ret = FAILURE;
    int ix = 0;
    int num = 0;
    int tot = 0;
    
    instance_t inst = {0};
    pthread_t tid;
    
    ASSERT(SUCCESS, ret = pthread_mutex_init(&inst.mutex, NULL));
    ASSERT(SUCCESS, ret = pthread_cond_init(&inst.cond, NULL));
    ASSERT(SUCCESS, ret = pthread_attr_init(&inst.attr));
    
    ASSERT(SUCCESS, ret = pthread_attr_setschedpolicy(&inst.attr, SCHED_OTHER));
    
    for ( ix = 0; ix < THREAD_NUM; ix++ ) {
        ASSERT(SUCCESS, ret = pthread_create(&tid, &inst.attr, __worker, &inst));
    }
    
    while ( 1 ) {
        printf("Input: < number > < money > \n");
        if ( fscanf(stdin, "%d %d", &num, &tot) == EOF ) {
            break;
        }
        if ( num < 0 || num > 10 ) {
            printf("Package number out of range: [1, 10]\n");
            continue;
        }
        else if ( tot < 0 || tot > 200 ) {
            printf("Total money out of range: [1, 200]\n");
            continue;
        }
        
        pthread_mutex_lock(&inst.mutex);
        item.num = num;
        item.tot = tot * 100;
        printf("Init: [%d, %d.%02d]\n", 
            item.num, item.tot / 100, item.tot % 100); 
            
        pthread_cond_broadcast(&inst.cond); 
        pthread_mutex_unlock(&inst.mutex);
        
        sleep(1);
    }
    
    ASSERT(SUCCESS, ret = pthread_attr_destroy(&inst.attr));
    ASSERT(SUCCESS, ret = pthread_cond_destroy(&inst.cond));
    ASSERT(SUCCESS, ret = pthread_mutex_destroy(&inst.mutex));
_E1:
    return EXIT_SUCCESS;
}

对于红包操作为共享资源,所以得用线程锁进行保护,各个子线程抢完红包后挂起休眠;
static void *__worker(void *args)
{
    int money = 0;
    
    instance_t *pinst = (instance_t *)args;
    
    if ( !args ) {
        return NULL;
    }
    
    printf("Start thread: %u\n", (u32)pthread_self());
    
    while ( 1 ) {
        pthread_mutex_lock(&pinst->mutex);
        pthread_cond_wait(&pinst->cond, &pinst->mutex); 
        
        if ( item.num <= 0 ) {
            printf("Thread #%d get %d.%02d, left [%d, %d.%02d]\n", 
                (u32)pthread_self(),
                0, 0, 0, 0, 0);
            pthread_mutex_unlock(&pinst->mutex);
            continue;
        }
        else if ( item.num == 1 ) {
            money = item.tot;
        }
        else {
            /* roll is 0.01 ~ 1.99 */
            money = item.tot * __RAND(1, 199) / 100 / item.num;
        }
        
        item.tot -= money;
        item.num--;
        printf("Thread #%d get %d.%02d, left [%d, %d.%02d]\n",
            (u32)pthread_self(),
            money / 100, money % 100, item.num, item.tot / 100, item.tot % 100);
            
        pthread_mutex_unlock(&pinst->mutex);
    }
    
    printf("Stop thread: %u", (u32)pthread_self());
    return NULL;
}

四、总结

本文通过一生产者多消费者的多线程编程去模拟微信抢红包的过程;
从执行结果上看,10个线程同时抢红包,每次发5个红包抢到的结果均是比较贴近手气红包的规律;
而且有个非常有趣的现象,每次居然都是前5个线程手快抢到了红包!修改了线程优先级也是一样的结果,所以猜测与广播唤醒的顺序机制有关;


参考文章:
[1] 微信红包金额分配的算法,http://www.open-open.com/lib/view/open1430473257443.html
[2] 微信红包的架构设计简介,https://www.zybuluo.com/yulin718/note/93148
[3] linux下条件变量、线程锁的使用,http://www.cppblog.com/converse/archive/2009/01/15/72064.aspx

猜你喜欢

转载自blog.csdn.net/stayneckwind2/article/details/54773532
今日推荐