WorkingWithThreads

Introduction

We'll need a threading recipe, this is the old Mandelbrot frame a number of us were working on way back when. Not really heavily threaded, but only thing I have that uses normal threads (not micro-threads).

See also LongRunningTasksMainLoopAsThread.

 

Example

 

切换行号显示
#! /usr/bin/env python
#############################################################################

"""
Mandelbrot program, for no particular reason.

John Farrell: initial version
Robin Dunn: introduced wxImage.SetData instead of wxBitmapFromData
Ionel Simionescu: used the Numeric package, and rewrote all of the
                  computation and data displaying code
Alexander Smishlajev: suggestions on optimising loops and drawing
Markus Gritsch: in-place calculation in the mandelbrot while loop
Mike Fletcher: minor changes

[06/24/09] Cody Precord: Cleanup, get it working with wxPython2.8,
                         fixes for thread safety,
                         and fixes for old deprecated/obsolete code.

"""

import wx
import threading
# TODO update to use numpy instead of old Numeric package
import numpy.oldnumeric as Numeric

class MandelbrotGenerator:
        """Slightly slower mandelbrot generator, this one uses instance
        attributes instead of globals and provides for "tiling" display

        """
        def __init__(self, width=200, height=200,
                     coordinates=((-2.0,-1.5), (1.0,1.5)),
                     xdivisor=0, ydivisor=0,
                     iterations=255,
                     redrawCallback=None,
                     statusCallback=None):

            self.width = width
            self.height = height
            self.coordinates = coordinates

            ### should check for remainders somewhere and be a little
            # smarter about valid ranges for divisors (100s of divisions
            # in both directions means 10000s of calcs)...
            if not xdivisor:
                xdivisor = 1 #width/50 or 1
            if not ydivisor:
                ydivisor = 10 #height/50 or 1
            self.xdivisor = xdivisor
            self.ydivisor = ydivisor
            self.redrawCallback = redrawCallback
            self.statusCallback = statusCallback or self.printStatus
            self.MAX_ITERATIONS = iterations
            self.data = Numeric.zeros(width*height)
            self.data.shape = (width, height)

            ### Set up tiling info, should really just do all the setup here and use a
            ### random choice to decide which tile to compute next
            self.currentTile = (-1,0)
            self.tileDimensions = ( width/xdivisor, height/ydivisor )
            self.tileSize = (width/xdivisor)* (height/ydivisor)
            (xa,ya), (xb,yb) = coordinates
            self.x_starts = Numeric.arange( xa, xb+((xb-xa)/xdivisor), (xb-xa)/xdivisor)
            self.y_starts = Numeric.arange( ya, yb+((yb-ya)/ydivisor), (yb-ya)/ydivisor)

        def DoAllTiles( self ):
            while self.DoTile():
                pass

        def DoTile(self, event=None):
            """Triggered event to draw a single tile into the data object"""
            x_index, y_index = self.currentTile
            if x_index < self.xdivisor - 1:
                self.currentTile = x_index, y_index = x_index+1, y_index
            elif y_index < self.ydivisor-1:
                self.currentTile = x_index, y_index = 0, y_index+1
            else:
                if self.redrawCallback is not None:
                    self.redrawCallback(self.data, False)
                return False

            print 'starting iteration', x_index, y_index
            coords = ((self.x_starts[x_index],self.y_starts[y_index]),
                      (self.x_starts[x_index+1],self.y_starts[y_index+1]),)
            part = self._tile( coords )
            part.shape = self.tileDimensions[1], self.tileDimensions[0]

            xdiv = self.width / self.xdivisor
            ydiv = self.height / self.ydivisor
            from_idx = ydiv * y_index
            self.data[from_idx : ydiv * (y_index+1), xdiv * x_index: xdiv * (x_index+1), ] = part
            if self.redrawCallback:
                self.redrawCallback(self.data, True) # there may be more to do...
            return True

        def printStatus(self, *arguments ):
            pass #print arguments

        def _tile(self, coordinates):
            """Calculate a single tile's value"""
            (c, z) = self._setup(coordinates)
            iterations = 0
            size = self.tileSize
            i_no = Numeric.arange(size)       # non-overflow indices
            data = self.MAX_ITERATIONS + Numeric.zeros(size)
            # initialize the "todo" arrays;
            # they will contain just the spots where we still need to iterate
            c_ = Numeric.array(c).astype(Numeric.Complex32)
            z_ = Numeric.array(z).astype(Numeric.Complex32)
            progressMonitor = self.statusCallback

            while (iterations < self.MAX_ITERATIONS) and len(i_no):
                # do the calculations in-place
                Numeric.multiply(z_, z_, z_)
                Numeric.add(z_, c_, z_)
                overflow = Numeric.greater_equal(abs(z_), 2.0)
                not_overflow = Numeric.logical_not(overflow)
                # get the indices where overflow occured
                ####overflowIndices = Numeric.compress(overflow, i_no) # slower
                overflowIndices = Numeric.repeat(i_no, overflow) # faster

                # set the pixel indices there
                for idx in overflowIndices:
                    data[idx] = iterations

                # compute the new array of non-overflow indices
                i_no = Numeric.repeat(i_no, not_overflow)

                # update the todo arrays
                c_ = Numeric.repeat(c_, not_overflow)
                z_ = Numeric.repeat(z_, not_overflow)
                iterations = iterations + 1
                progressMonitor(iterations, 100.0 * len(i_no) / size)
            return data

        def _setup(self, coordinates):
            """setup for processing of a single tile"""
            # we use a single array for the real values corresponding to the x coordinates
            width, height = self.tileDimensions
            diff = coordinates[1][0] - coordinates[0][0]
            xs = 0j + (coordinates[0][0] + Numeric.arange(width).astype(Numeric.Float32) * diff / width)

            # we use a single array for the imaginary values corresponding to the y coordinates
            diff = coordinates[1][1] - coordinates[0][1]
            ys = 1j * (coordinates[0][1] + Numeric.arange(height).astype(Numeric.Float32) * diff / height)

            # we build <c> in direct correpondence with the pixels in the image
            c = Numeric.add.outer(ys, xs)
            z = Numeric.zeros((height, width)).astype(Numeric.Complex32)

            # use flattened representations for easier handling of array elements
            c = Numeric.ravel(c)
            z = Numeric.ravel(z)
            return (c, z)

#### GUI ####
class MandelCanvas(wx.Window):
    def __init__(self, parent, id=wx.ID_ANY,
                 width=600, height=600,
                 coordinates=((-2.0,-1.5),(1.0,1.5)),
                 weights=(16,1,32), iterations=255,
                 xdivisor=0, ydivisor=0):
        wx.Window.__init__(self, parent, id)

        # Attributes
        self.width  = width
        self.height = height
        self.coordinates = coordinates
        self.weights = weights
        self.parent = parent
        self.border = (1, 1)
        self.bitmap = None
        self.colours = Numeric.zeros((iterations + 1, 3))
        arangeMax = Numeric.arange(0, iterations + 1)
        self.colours[:,0] = Numeric.clip(arangeMax * weights[0], 0, iterations)
        self.colours[:,1] = Numeric.clip(arangeMax * weights[1], 0, iterations)
        self.colours[:,2] = Numeric.clip(arangeMax * weights[2], 0, iterations)

        self.image = wx.EmptyImage(width, height)
        self.bitmap = self.image.ConvertToBitmap()
        self.generator = MandelbrotGenerator(width=width, height=height,
                                             coordinates=coordinates,
                                             redrawCallback=self.dataUpdate,
                                             iterations=iterations,
                                             xdivisor=xdivisor,
                                             ydivisor=ydivisor)

        # Setup
        self.SetSize(wx.Size(width, height))
        self.SetBackgroundColour(wx.NamedColour("black"))
        self.Bind(wx.EVT_PAINT, self.OnPaint)

        # Start generating the image
        self.thread = threading.Thread(target=self.generator.DoAllTiles)
        self.thread.start()

    def dataUpdate(self, data, more=False):
        if more:
            data.shape = (self.height, self.width)
            # build the pixel values
            pixels = Numeric.take(self.colours, data)
            # create the image data
            bitmap = pixels.astype(Numeric.UnsignedInt8).tostring()

            # create the image itself
            def updateGui():
                """Need to do gui operations back on main thread"""
                self.image.SetData(bitmap)
                self.bitmap = self.image.ConvertToBitmap()
                self.Refresh()
            wx.CallAfter(updateGui)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.BeginDrawing()
        if self.bitmap != None and self.bitmap.IsOk():
            dc.DrawBitmap(self.bitmap, 0, 0, False)
        dc.EndDrawing()

class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)

        self.CreateStatusBar()
        self.Centre(wx.BOTH)
        mdb = MandelCanvas(self, width=400, height=400, iterations=255)

        # Layout
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(mdb, 0, wx.EXPAND)
        self.SetAutoLayout(True)
        self.SetInitialSize()

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, wx.ID_ANY, "Mandelbrot")
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

if __name__ == '__main__':
    app = MyApp(0)
    app.MainLoop()

 

Display

 


Here's a different approach to using a thread for long running calculations. This demo was modified to help show how, where and when the calculation thread sends its various messages back to the main GUI program. The original app is called wxPython and Threads posted by Mike Driscoll on the Mouse vs Python blog.

The "calculations" are simulated by calling time.sleep() for a random interval. Using sleep() is safe because the thread function isn't part of the wx GUI. The thread code uses function Publisher() from wx.lib.pubsub to send data messages back to the main program. When the main program receives a message from the thread it does a little bit of data decoding to determine which of four kinds of message it is. The GUI then displays the data appropriate for its decoded message type. Receiving messages is handled as events so the main program's MainLoop() can go on doing whatever else it needs to do. However, this demo is so simple that there happens to be nothing else to do in the meantime.

 

切换行号显示
   1     def DisplayThreadMessages( self, msg ) :
   2         """ Receives data from thread and updates the display. """
   3 
   4         msgData = msg.data
   5         if isinstance( msgData, str ) :
   6             self.displayTxtCtrl.WriteText( 'Textual message = [ %s ]\n' % (msgData) )
   7 
   8         elif isinstance( msgData, float ) :
   9             self.displayTxtCtrl.WriteText( '\n' )   # A blank line separator
  10             self.displayTxtCtrl.WriteText( 'Processing time was [ %s ] secs.\n' % (msgData) )
  11 
  12         elif isinstance( msgData, int ) :
  13 
  14             if (msgData == -1) :    # This flag value indicates 'Thread processing has completed'.
  15                 self.btn.Enable()    # The GUI is now ready for another thread to start.
  16                 self.displayTxtCtrl.WriteText( 'Integer ThreadCompletedFlag = [ %d ]\n' % (msgData) )
  17 
  18             else :
  19                 self.displayTxtCtrl.WriteText( 'Integer Calculation Result = [ %d ]\n' % (msgData) )
  20         #end if
  21 
  22     #end def

TypicalRun.png

A much more flexible message encoding/decoding scheme can easily be substituted with just a little more ingenuity. See LongRunningTasks for a much more in-depth look at threading and also two-way communication. - Ray Pasco

 

Display

posted @ 2013-09-05 23:15  dengyigod  阅读(341)  评论(0编辑  收藏  举报