Python3编程实战Tetris机器人(多线程问题)

系列文章入口

《Python3编程实战Tetris机器人》

发现问题

在测试过程中,发现程序出错,但关闭定时器,不进行自动下落就不会有问题。原因是Timer会新开一个线程,线程和主线会产生资源冲突。

解决方案

首先想到的是加锁,游戏逻辑很简单,加锁应该很容易解决问题。但不管我粗粒度加,还是尽量细粒度加,最后都会死锁。最后进行打印,发现程序停在了tkinter.Canvas.move处,个人认为这是tkinter的bug。
此路不通,换个思路。开一个工作线程,来完成所有的操作,主线程与定时器操作,都只是往工作线程中提交任务。也就是只让一个工作线程来做任务,这样就把资源冲突的问题避开了。

加锁

加锁方案分析

键盘响应加锁

tickLock[0] = True
with curTetrisLock:
    print("-------+++---00000000--- get lock", tickLock)
    if ke.keysym == 'Left':
        self.game.moveLeft()
    if ke.keysym == 'Right':
        self.game.moveRight()
    if ke.keysym == 'Up':
        self.game.rotate()
    if ke.keysym == 'Down':
        self.game.moveDown()
    if ke.keysym == 'space':
        self.game.moveDownEnd()
print("-------+++---00000000--- lose lock", tickLock)

定时器响应加锁

def tickoff(self):
    if self.gameRunningStatus == 1:
        if not tickLock[0]:
            with curTetrisLock:
                print("------------------ get lock", tickLock[1])
                self.moveDown()
            print("================== lose lock", tickLock[1])
        self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)
        self.tick.start()

问题定位

程序最后停在了Block类中的tkinter.Canvas.move处,每次都由定时器触发,无法释放。

有兴趣的同学可以到项目中,切换到lockbug分支去研究,我写了很多打印输出方便问题定位。

增加工作线程

任务单元设计

新增一个Queue,键盘响应与定时器响应往队列中增加任务单元,工作线程逐一处理这些任务。任务单元如下设计:

("cmd",(data))

每一个任务单元都是一个二元元组(方便数据解构),第一个是字符串,为命令;第二个是元组,是数据包(也按方便解构的方式去设计),由每个命令自行定义。

工作线程

def opWork(self):
    while True:
        if not opQueue.empty():
            cmd,data = opQueue.get()
            if op == "Left":
                self.moveLeft()
            elif op == "Right":
                self.moveRight()
            elif op == "Up":
                self.rotate()
            elif op == "Down":
                self.moveDown()
            elif op == "space":
                self.moveDownEnd()
            elif op == "quit":
                break
        else:
            time.sleep(0.01)

键盘响应改造

def processKeyboardEvent(self, ke):
    if self.game.getGameRunningStatus() == 1:
        if ke.keysym == 'Left':
            opQueue.put(('Left',()))
        if ke.keysym == 'Right':
            opQueue.put(('Right',()))
        if ke.keysym == 'Up':
            opQueue.put(('Up',()))
        if ke.keysym == 'Down':
            opQueue.put(('Down',()))
        if ke.keysym == 'space':
            opQueue.put(('space',()))

定时器改造

游戏控制主要函数,在方块下落到底部后,进行消层、统计得分、速度等级判定、游戏是否结束判定以及将下一方块移入游戏空间并再生成一个方块显示在下一方块显示空间中。

def tickoff(self):
    if self.gameRunningStatus == 1:
        opQueue.put(('Down'),())
        self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)
        self.tick.start()

项目地址

https://gitee.com/zhoutk/ptetris
或
https://github.com/zhoutk/ptetris

运行方法

1. install python3, git
2. git clone https://gitee.com/zhoutk/ptetris (or download and unzip source code)
3. cd ptetris
4. python3 tetris

This project surpport windows, linux, macOs

on linux, you must install tkinter first, use this command:  
sudo apt install python3-tk

相关项目

已经实现了C++版,项目地址:

https://gitee.com/zhoutk/qtetris

全栈编程
自由程序员,技术路线c,delphi, c++,c#,java,php,node.js,python,golang,typescript;超喜欢re...

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢rea...

2.6k 声望
1.2k 粉丝
0 条评论
推荐阅读
node.js基于 cmake-js 进行插件开发实战
以前工作在node.js环境下,做微服务产品; 三年前转回到C++环境,已经有一些代码积攒。我将以往基于node.js与C++的相关项目结合起来(C++代码以addon插件嵌入),实现了一个微服务快速(rest api service)开发框...

zhoutk阅读 787

MongoDB 插入时间与更新时间(create_time/update_time)
MongoDB 在数据库层面不能像 MySQL 一样设置自动创建 create_time/update_time,自动更新 update_time

qbit阅读 13.8k评论 2

多线程学习-ThreadLocal
ThreadLocal用于多线程环境下每个线程存储和获取线程的局部变量,这些局部变量与线程绑定,线程之间互不影响。本篇文章将对ThreadLocal的使用和原理进行学习。

半夏之沫1阅读 992

多线程学习-锁
本篇文章将对基于AbstractQueuedSynchronizer实现的锁进行学习,同时对LockSupport和Condition的使用进行整理和分析。内容参考了《Java并发编程的艺术》第5章。在之前的多线程学习-队列同步器中已经对AbstractQue...

半夏之沫1阅读 805

多线程提升MySQL造数据速度
由于最近沉迷小孩子的MySQL 是怎样运行的:从根儿上理解 MySQL,想造一点数据磨练一下自己的所学,所以准备创建两个一百万数据的表,一开始写一个简单的程序使用jdbc往里面插入数据,开始的写的时候我已经意识到...

eacape1阅读 427

封面图
Python3 全能安装详解
小编今天折腾了一天,整个Python3 人工智能开发包。卡在pip 包管理器上大半天。找遍大部分资料,就搞不懂为嘛每篇文章就只写一个片面的知识点就不能汇总一下嘛。下面来啦,小编来整理一下,避免下次找不到了。微...

叶剑飞雪阅读 682

JAVA定时任务 - JDK Timer
一个JDK Timer的例子。JDK Timer包含的主要对象。Timer对象分析。TimerTask对象分析。任务调度:一次性定时任务。任务调度:多次执行的定时任务(固定时间点或固定时间间隔)。JDK Timer是单线程的吗?Thread和Ru...

阅读 664

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢rea...

2.6k 声望
1.2k 粉丝
宣传栏