天天看點

c語言多線程-模拟微信搶紅包

一、背景

想法源于微信、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個線程手快搶到了紅包!修改了線程優先級也是一樣的結果,是以猜測與廣播喚醒的順序機制有關;

c語言多線程-模拟微信搶紅包

參考文章: [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

繼續閱讀