随着互联网及移动技术的飞速发展,在线选座变得愈加便利。通过移动应用和网站,用户可以实时查看座位信息并作出选择,这种便捷性吸引了越来越多的人使用选座服务。
问题描述:模拟选座系统,有10个空座,12个用户同时选座,系统从空座中随机选择一个,打印出当前用户的序号和选中作为序号。
输出结果:10个用户成功选择十个不同的座位号,两个用户选座失败
容器准备
首先,确定我们的人数与座位数,选择合适的容器存储 用户选座结果 和 空座位序列号
#define SEAR_NUM 10
#define USER_NUM 12
int empty_seat[SEAT_NUM];//空闲座位容器
int users_seat[SEAT_NUM];//用户抢座结果
int remain = SEAT_NUM;//当前剩余空闲座位剩余数量
选座逻辑
任意一个用户在选择位置时,我们需要执行的选座逻辑:
1.如果剩余座位不足(reamin<=0),那么返回错误码,不能选座了
2.选座时在剩余的座位号中随机分配,获取剩余容器容量中随机一个索引 int random_idx = rand()%remain;
3.获得索引后,根据索引取出空闲座位号int seat_number = empty_seat[random_idx];
4.取走号码被弹出容器:
//由于我们开的是数组,所以无法直接弹出,常规方法在数据量大时开销也大
//所以我们选择:将空闲座位序列中最后一个索引对应的序列号填充到被选走的序列号索引位
//最后将剩余空闲位置remain--即可达到舍弃被选择座位删除操作
empty[random_idx] = empty_seat[--remain];
5.反馈座位号,成功选择。
据此,写出以下函数:
//选座,将座位号返回
int choose_seat(){
if(remain<=0)return -1;
int random_idx = rand()%remain;
int seat_number = empty_seat[random_idx];
empty_seat[random_idx] = empty_seat[--remain];
return seat_number;
}
单人选座
单人选座是单独打开一个线程,去执行线程函数,在线程函数中运行选座逻辑:
void *thread_choose_seat(void *arg){
int uid=*(int*)arg;//将本次选座的用户id传入
user_seat[uid] = choose_seat();//选座并记录
if(users_seat[uid]==-1)//选座失败
printf("用户 %d 选座失败,座位售罄\n",uid+1);
else printf("用户 %d 成功选得座位 %d\n",uid+1,users_seat[uid]);
return NULL;
}
多人选座
多人选座只需要执行users_num次的选座即可:
int main()
{
srand((unsigned)time(NULL));
for(int i=0;i<SEAT_NUM;i++)empty_seat[i]=i+1;//为每一个座位赋予座位号,默认升序
pthread_t user[USER_NUM];//多个用户即是多个线程
for(int i=0;i<USER_NUM;i++){
users_seat[i]=i;//用户座位处默认存储用户id,选座成功后自动填充座位号
//如果直接传入&i,后面main主线程i++后,i的值变化,易冲突,所以使用上面的暂存方式
pthread_create(&user[i],NULL,thread_choose_seat,&users_seat[i]);
}
for(int i=1;i<USER_NUM;i++){
pthread_join(user[i],NULL);//加入队伍并等待选座真正结束,避免主线程直接退出
}
return 0;
}
但是此时有一个致命的问题,就是在多个线程抢票时,remain变量和empty_seat数组均是共享资源,对其访问时,极易出现冲突。所以此处我们使用互斥锁将选座逻辑函数给锁起来:
为此,在全局区申请一个互斥量mtx,在主函数完成初始化以及主函数return结束前销毁互斥量。最后在线程函数中加锁保护即可。
void *thread_choose_seat(void *arg){
int uid=*(int *)arg; //提取用户id号
pthread_mutex_lock(&mtx); //加锁
users_seat[uid] = choose_seat(); //记录用户的座位号
pthread_mutex_unlock(&mtx); //解锁
if (users_seat[uid] == -1) printf("用户 %d 选座失败,座位售罄\n", uid+1);
else printf("用户 %d 成功选得座位 %d\n",uid+1,users_seat[uid]);
}
最终代码
#define SEAT_NUM 10
#define USER_NUM 12
int empty_seat[SEAT_NUM]; //空闲座位
int users_seat[USER_NUM]; //用户结果
int remain = SEAT_NUM; //剩余空闲位置
pthread_mutex_t mtx; //互斥量
int choose_seat(){
if(remain<=0)return -1; //如果剩余空闲位不足,返回-1,选座失败
int random_idx = rand()%remain; //生成随机序列号
int seat_number= empty_seat[random_idx]; //将序列号对应得座位号获取出来
empty_seat[random_idx] =empty_seat[--remain]; //将取走得座位号拿走,使用最后的座位号填充,并将剩余座位数量减一
return seat_number; //将座位号返回
}
void *thread_choose_seat(void *arg){
int uid=*(int *)arg; //提取用户id号
pthread_mutex_lock(&mtx); //加锁
users_seat[uid] = choose_seat(); //记录用户的座位号
pthread_mutex_unlock(&mtx); //解锁
if (users_seat[uid] == -1) printf("用户 %d 选座失败,座位售罄\n", uid+1);
else printf("用户 %d 成功选得座位 %d\n",uid+1,users_seat[uid]);
}
int main(int argc, char* argv[])
{
srand((unsigned)time(NULL)); //种下随机数种子
pthread_mutex_init(&mtx,NULL); //初始化互斥量
for(int i=0;i < SEAT_NUM;i++)empty_seat[i]=i+1; //为每一个座位赋予座位号
pthread_t user[USER_NUM];
for(int i=0;i<USER_NUM;i++){
users_seat[i]=i;
pthread_create(&user[i],NULL,thread_buy_ticket,&users_seat[i]);
} //加入抢票队伍
for(int i=0;i<USER_NUM;i++){
pthread_join(user[i],NULL);
} //等待抢票--等待所有线程都将选票执行完毕再执行后面操作
pthread_mutex_destroy(&mtx); //销毁互斥量
return 0;
}
成果展示
项目总结
本项目主要通过在Linux(Ubuntu)环境下使用多线程编程,利用互斥量(互斥锁)的线程同步机制,避免了多个用户同时竞争资源时造成的资源不同步问题。从而完成了对用户选取座位的功能,使用srand使随机种子以当前系统时间为准,使得随机函数rand生成的随机数不是伪随机数。
选座时涉及到一个简单算法:交换尾数据与序列任一数据,长度计数减一,从而达到尾删的效果,因为尾数据无用,所以不交换,直接选择填充覆盖。
(算法思想来源:)
1.有效区与无效区的划分:取自于--排序算法的有序区和无序区的划分
2.覆盖处理方式:取自于C++中STL-unique函数去重时对重复元素的处理方式
3.长度计数操作:取自于C++容器源码拟实现时,尾部指针自减删除无效区的理论
拓展:选座系统、抢票系统、抢课系统、限量抢购系统等均可以采用该项目形式完成基本功能架构
感谢大家!