用 PyQT 实现计算PI的小程序 07 Jun, 2012

本期操作系统理论课,在讲解线程时布置了一个课后实验作业:写一个可以计算PI的小程序,用户输入需要计算的位数,点击开始后,便开始计算,并实时显示在界面上,用户点击暂停后,计算将暂停,当然重新开始后,可以接着计算。

因为计算PI是一个比较耗时的任务,而且需要随时可以暂停,于是就需要使用多线程,程序主界面做为主线程,然后计算PI可以作为另一个线程,这样主界面就不用受制于PI的计算,从而不会出现卡顿等不正常的问题。当然,可以使用系统级的线程,不过这样就比较麻烦了,实现起来还是比较困难的,不过既然需要用到界面开发,相信很多开发工具会提供更加易用的线程处理方法。于是现在有两个问题需要解决:

  1. 界面设计支持,并提供易用的线程处理方法
  2. PI的计算

对于第一个问题,因为在 Linux 下,首先想到的是 GTK,不过 GUI 程序设计最麻烦的还是界面的布局了,在和 Qt 比较后,选择了 Qt,因为 Qt 中有一个例子,界面和我想象的差不多,稍微修改一下便可,而且 Qt 中对线程的处理方法我比较喜欢, GTK 的看着觉得混乱。

第二个问题,需要控制PI的小数位,很多算法并不能控制小数位的生成,不过最后找到了spigot algorithm这个算法,而且还有一个 Python 的实现,这个实现可以一位一位的计算出PI的值,简直就和要求搭配的天衣无缝,更何况是使用的 Python 呢。算法如下:

def pi_digits():
    """generator for digits of pi"""
    q,r,t,k,n,l = 1,0,1,1,3,3
    while True:
        if 4*q+r-t < n*t:
            yield n
            q,r,t,k,n,l = (10*q,10*(r-n*t),t,k,(10*(3*q+r))/t-10*n,l)
        else:
            q,r,t,k,n,l = (q*k,(2*q+r)*l,t*l,k+1,(q*(7*k+2)+r*l)/(t*l),l+2)

pi = pi_digits()

这将返回一个生成器,于是以后不断的调用pi.next()便可以一位一位的生成 Pytho的值了。

这下就确定使用 PyQt 进行开发。先展示一张效果图,在 Ubuntu 下:

已经计算完的界面

PyQT 采用了 Qt 的线程处理方式,Qt 提供一个叫QThread类,该类提供了创建一个新线程以及控制线程运行的各种方法。线程是通过重写该类下的run方法开始执行的。在 Qt 系统中,始终运行着一个 GUI 主事件线程,这个主线程从窗口系统中获取事件,并将它们分发到各个组件去处理。

对于窗口系统的事件分发, QT采用的信号\槽模式,主线程和子线程之间的通信也可以使用信号\槽模式,在这个例子中,子线程向主线程发送数据便是采用的这个方法。在实现两个不同的线程对共有数据的互斥访问处理上,Qt 还提供了QMutex类来实现锁机制。当需要结束子线程时,需要使用QThread类中的wait方法将其阻塞,然后等待操作系统的调度将其回收。

在实现中,因为计算PI的位数需要使用用户的输入值,因此为了便于可以多次使用,子线程是在用户点击开始后才创建的,创建时初始化位数,在点击重置后,便撤销掉这个线程。而在撤销线程时,就比较麻烦了,需要先断掉与这个线程连接的槽,然后停止,然后阻塞,最后等操作系统的调度将其删除。下面就是撤销一个线程的代码:

self.disconnect(self.timer, QtCore.SIGNAL("updateresult"),
                            self.updateResult)
self.timer.stop()
self.timer.quit()
self.timer.wait()
self.timer.deleteLater()

小记:

  1. 这个计算PI的算法很是方便,于是便给同学推荐,因为他使用的是MFC,于是尝试改用C来实现,不过怎么都有问题,经过在 Python 上调试才发现,进行循环后,后面的数将越来越大,已经不是C语言的基本类型所能容纳的,看来这个算法只适合支持大整数的语言。
  2. PyQt 真心方便啊, Python 真心简洁啊,这个程序只用了150行,大大的节省了打印纸张数,为环保做了大贡献(现在还不理解为什么需要交打印稿,直接电子稿不环保的多嘛)。

全部程序代码在这里,欢迎参观:RockPI