申时全(广东科技学院计算机系,广东东莞523000)
Linux多线程编程技术在掷骰子游戏模拟程序中的应用
申时全
(广东科技学院计算机系,广东东莞523000)
为了模拟概率事件,针对掷骰子游戏规则,应用Linux系统下C语言多线程机制以及多个二值信号量以实现多个线程间循环同步。通过伪随机数模拟掷骰子的点数,设计并实现了一个基于多线程方式模拟4人掷骰子游戏程序,并对1 000次游戏中每个游戏者获胜的次数进行统计。可以看出,在多次游戏中,每个游戏者获胜的概率符合概率分布规律。程序运行结果表明,利用信号量可有效实现多个线程间的同步与互斥,并简化了程序结构。
多线程;线程同步;随机数;掷骰子游戏程序
概率事件是日常生活中经常会遇到的,如出现某种状况的可能性,产品出现故障的几率等。本文通过一个模拟掷骰子游戏程序来模拟人们在某种博弈规则下的获胜概率。采用线程编程模式,用一个线程模拟一个游戏者掷下6个骰子,并按一定规则给出“叫点”数。通过1 000次游戏,统计出每个游戏者获胜次数N,则获胜概率为N/1 000。
线程是Linux系统的一个执行序列,其处于进程中,多个线程共享同一进程的存储空间和资源。操作系统以进程为单位分配资源并进行调度。但在多进程并发运行的系统中,进程调度开销比较大[1]。按一般定义:线程是一个进程内部的一个控制序列。在一个进程中创建新的线程运行时,该线程会拥有自己的运行栈,并与创建它的线程共享全局变量等系统资源。一个进程中的多个线程可以处于并发运行状态。因此,要使得一个进程中多个线程有序地工作,并有效地共享资源,就需要在线程之间进行有效的同步和互斥控制[2]。Linux系统提供了多种手段实现进程间、线程间的同步和互斥。本文介绍Linux系统下进行多线程编程中线程创建、线程挂起、线程同步和互斥等有关问题,设计了一个模拟4人进行掷骰子游戏的程序,说明了多线程编程中的同步与互斥编程技术。
为了实现游戏中掷骰子点数的随机性,需要用到伪随机数生成函数。伪随机数在很多领域中都有应用[3]。通过C标准库中随机函数rand()及相关函数的应用,给出解决指定范围随机整数生成通用方法。
通过指定一个较大的游戏次数(如1 000),可以统计出各游戏者获胜概率,按照随机数的出现概率,则每个游戏者获胜次数相差不会太大(当然也会有例外)。
在Linux系统中,线程系统调用函数定义在Pthread.h中[2]。因此在程序中应有如下指令:
#inc1ude
1.1 与线程编程相关的几个常用函数
1.1.1 线程创建函数
建立线程的函数Pthread_create(),函数原型定义为:
int Pthread_create(Pthread_t*tid,const Pthread_attr_t*attr,void *(*start_rtn)(void),void *arg);
参数tid是一个指向Pthread_t类型指针,如果创建线程成功,则在该指针所指变量中写入线程的标识符(ID号);参数attr是指向线程属性的结构体指针,一般无需设定,只要设置为NULL即可;参数start_rtn用来传递一个函数地址,该函数应返回一个任意类型指针,该参数用一个定义了的函数名设置即可;参数arg是传递给函数的参数指针,可以为任何类型。
1.1.2 线程退出函数
线程退出函数原型定义为:
void Pthread_exit(void *retva1);
通过调用该函数终止线程执行,返回一个指向某对象的指针(注意不能用于返回指向局部变量的指针)。
1.1.3 使线程挂起的函数
函数原型定义为:
int Pthread_join(Pthread_t thread,void **thread_rtn);
参数thread指定要等待的线程;参数thread_rtn是一个指针,指向另一个指针,该指针指向线程返回值。
1.1.4 获得本线程lD的函数
函数原型定义为:
Pthread_t Pthread_se1f(void);
通过调用该函数,可获得当前执行的线程标识符(ID号)。
1.1.5 判断两个线程是否为同一线程的函数
函数原型定义为:
int Pthread_equa1(Pthread_t Pid1,Pthread_t Pid2);
1.2 线程同步与互斥的几个函数
在Linux系统中,有关进程、线程同步与互斥的手段有多种,这里只涉及有关的信号量函数[4]。信号量类型sem_t及相关函数定义在semaPhore.h中,因此在程序头部应包含#inc1ude
1.2.1 创建信号量函数sem_init()
函数原型定义:
int sem_init(sem_t*sem,int Pshared,unsigned inti va1ue);
该函数初始化一个信号量,参数sem是指向信号量的指针;参数Pshared为0指示该信号量是当前进程的局部信号量,在线程编程中,该参数置为0;参数va1ue是信号量的值。
1.2.2 控制信号量的函数
函数原型定义如下:
int sem_wait(sem_t*sem);int sem_Post(sem_t*sem);
这两个函数分别对信号量sem执行P操作和V操作。两个函数的参数都是一个sem_t类型指针,指向由sem_init调用初始化的信号量。
1.2.3 销毁信号量函数
函数原型定义为:
int sem_destroy(sem_t*sem);
用完一个信号量后应销毁该信号量,并清理相关资源。该函数以一个信号量指针为参数,清理该信号量拥有的所有资源并销毁这个信号量。
2.1 游戏规则定义
假定有4个游戏参与者,每人轮流掷下5个骰子,然后找出点数相同最多的点数,例如5个骰子中,出现最多的是3个4点,那就给出一个“叫点数”,这个叫点数就是出现相同点数最多的个数加1及点数,如3个4点,则“叫点数”为(4,4)。规定所有1点可以代替其他任意点数,如有2个1点,3个3点,则可叫5个3点。最后总点数(个数乘点数)最大者为获胜者,若在一轮游戏中,有2个以上具有相同点数(最大),则多人同时获胜,其余游戏者为失败。这个规则由程序模拟,与实际游戏中规则有些不同。
2.2 程序功能定义
该模拟程序应先输入游戏者姓名,然后在屏幕上开列4个显示窗口,用于显示每个游戏者的点数分布(5个)、叫点数、总盘数、获胜计数值。
2.3 程序实现技术
为了使用户界面良好,使用Linux系统库curses支持,使用该库中的输出函数实现窗口数据输出。另外需要用到如下技术:
(1)链表技术
在许多情况下,使用循环链表作为数据存储便于程序访问[5]。用一个单向循环链表存储游戏用户的数据,定义节点结构如下:
tyPedef struct UserNode{
char name[21]; //用户名字
int count; //累计次数
int score[MAX_NUM]; //存放每次点数
int win_count; //累计获胜次数
Struct UserNode *next;}Node_tyPe;
把4个游戏者用户节点组成一个带头节点的循环链表结构,如图1所示。
图1 游戏者用户链表
(2)安全输入技术
为了输入用户名,且必须在指定屏幕位置输入,用户输入时不能超过限定字符个数(例如20),否则会出现运行错误。因此不能使用常规标准库函数gets()输入,而是另外编写一个函数GetString(char*str,int 1en)来实现。该函数中,通过调用Linux系统无回显字符输入函数getch()读取字符,并排除非法字符,限制输入字符数小于或等于参数1en。其源程序实现限于篇幅不再赘述。
(3)输入游戏者姓名创建用户链表结构
程序中定义一个用于建立链表的函数Node_tyPe* creat_List(int n),这个函数建立具有n个用户节点的循环链表,返回链表头指针。该函数调用前面给出的函数Get-String()输入游戏者姓名。
(4)生成随机数问题
在C语言的标准库中定义了随机数生成函数rand(),用于生成0~RAND_MAX的整数。程序采用单向函数反复迭代,周期性地输出伪随机序列[3]。一般,如果要生成一个给定范围(例如1~9)的随机数,都会使用如下语句:
rnd_num=rand()%9 +1;
这样不符合随机分布原则。为了防止运行程序每次产生的都是同一随机数列,有必要初始化随机种子。使用srand((int)time(NULL))来将伪随机数生成器初始化为某一个不可预测点,在程序初始化时执行。
下面给出一个用于产生给定范围的随机数函数。
int Random Int(int 1ow,int high){
int rnd; doub1e d;
d =(doub1e)rand()/((doub1e)RAND_MAX+1);
rnd =(int)(d*(high -1ow+1));
return rnd;
}
(5)多窗口显示技术
为了在每个独立窗口显示一个游戏用户线程状态数据,需要用到Linux中curses库,该库支持头文件curses.h。支持窗口显示的有关函数定义在这个头文件中。下面列出几个相关函数:
创建窗口函数,函数原型:
W INDOW *newwin(int 1ine,int co1s,int start_y,int start_x);
在窗口指定位置进行格式化输出,函数原型:
intmvwPrintw(W INDOW *win,int row,int co1,char*format,…);
限于篇幅,其他函数不再列出。
(6)如何解决程序中线程同步和互斥问题
整个游戏程序由4个游戏者用户线程和一个主线程构成。主线程和4个游戏者用户线程的关系是:主线程做好初始化工作,创建4个游戏者线程,然后做好初始准备,进入游戏循环控制。因为游戏者线程一旦创建就会开始执行,所以必须处理好主线程与各个游戏用户线程之间的同步关系。每个线程用2个信号量实现同步,通过参数传递方式将信号量传到线程中,程序中设置5个共享的sem _t信号量。同步顺序关系如图2所示。
图2 各线程间的同步关系
对于多线程程序,每个线程都可并发运行,但对于访问共享数据必须是互斥访问,即满足互斥关系[6]。使用一个互斥信号量实现共享数据的互斥访问。主线程必须使第一个游戏者线程正确进入,然后是第二个、第三个、第四个游戏者线程执行,产生游戏数据并修改了状态数据后,主线程处理结果数据,判定每个游戏者胜负,修改其获胜统计值,然后进入下一轮游戏。通过共享一个全局工作指针work实现节点数据修改。
对于4个游戏者线程的实现,可以分别实现4个线程控制序列,即定义4个线程函数。由于每个线程行为是一致的,可以在创建线程时传递一个变量i的指针给线程实现同步。
创建线程语句:
Pthread_crete(&Pid[i-1],NULL,gamer,(void *)&i);
在屏幕上实现多窗口显示效果,显示游戏者状态数据,程序中模拟4个游戏者轮流掷骰子MAX_NUM(最多1 000)次,线程负责生成5个随机数(1~6)表示掷下5个骰子。用一个全局指针变量work指向每个线程的信息节点。一轮游戏结束,work指针指向头节点,主线程则在处理一轮游戏的胜负决断后,将work指向首节点,开始下一轮游戏。主线程程序结构如图3所示,游戏者线程程序结构如图4所示。
图3 主线程程序结构
图4 游戏者线程程序结构
运行这个程序需要用到curses库和Pthread库,编译时使用选项-1Pthread-1curses。经过程序运行,表明采用的同步控制方法是有效的,获得了预期效果。表1所示为其中一组运行结果。
表1 程序运行结果
模拟4人进行掷骰子游戏的多线程游戏程序验证了随机数的统计性能,也说明了多线程编程方法的可行性。通过多线程编程可以很好地解决并发性问题[6]。本文给出的模拟程序工作模式,对于具有多个循环控制对象的系统的应用编程具有参考价值[7],只要将相关操作语句更换成循环控制节点对象的控制(测量)操作即可,其程序采用的多线程同步方法是通用的[8]。另外,如果将此程序移植到网络模式,每个线程改为与实际游戏者进行通信的程序语句方式,就可以实现网络下的游戏程序,可以把叫点过程改为接收远程游戏者输入的叫点数。当然,要实现网络模式下的游戏程序还有许多工作要做。在具有多核处理器情况下采用多线程编程将会获得更高的运行效率。
[1]何宏宇,刘正熙,陈正茂.基于Linux的多进程MDSL研究与设计[J].四川大学学报(自然科学版),2013,50(1):46-50.
[2]刘金,胡创,胡明,等.多线程环境下基于多预取点的文件预取[J].计算机应用,2012,32(6):1713-1716,1720.
[3]高树静,曲英杰,宋廷强.基于单向函数的伪随机数发生器[J].计算机研究与发展,2015,52(6):1394-1399.
[4]彭玉柱.基于多线程机制的电力数据采集系统设计与实现[J].计算机应用与软件,2015,32(1):78-81.
[5]何先波,李明东,王锦,等.Linux操作系统中通用双向循环链表的实现分析[J].西华师范大学学报(自然科学版),2012,33(2):213-217.
[6]谢文斌,陈学适,姜忠鼎.并行绘制游戏系统中同步传输协议的设计与实现[J].计算机应用与软件,2014,31(10):99-103.
[7]赵源,姜小峰.基于多线程技术的自动测试系统优化设计[J].计算机应用,2014,34(7):2124-2128.
[8]吴宇佳,浦伟,周妍,等.Linux下多线程数据采集研究与实现[J].信息安全与通信保密,2012(7):92-94.
APP1ication of Linux mu1ti thread Programming techno1ogy in the simu1ation Program of dice game
Shen Shiquan
(DePartment of ComPuter,Guangdong University of Science&Techno1ogy,Dongguan 523000,China)
In order to simu1ate the Probabi1ity events,according to the ru1e of dice game,themu1ti thread mechanism in the C 1anguage under Linux system,and the mu1tiP1e two va1ue semaPhore are aPP1ied to rea1ize cyc1e synchronous among mu1tiP1e threads.The Program simu1ating 4 PeoP1e dice game,which is based on mu1ti thread mode1,is designed and imP1emented.Each P1ayer's winning numbers in 1 000 rounds of the game are ca1cu1ated through simu1ating the Points of the dice with Pseudo random number.As can be seen in the game,each P1ayer has simi1ar winning Probabi1ity in most cases.The resu1ts show that using semaPhores can effective1y achieve synchronization and mutua1exc1usion among mu1tiP1e threads,and can simP1ify the Program structure.
mu1ti thread;thread synchronous;random number;dice game Program
TP311.1
A
10.19358 /j.issn.1674-7720.2016.09.025
申时全.Linux多线程编程技术在掷骰子游戏模拟程序中的应用[J].微型机与应用,2016,35(9):85-88.
2016-01-14)
申时全(1953 -),男,本科,教授,主要研究方向:计算机应用、软件设计技术。