Why does this code crash python?

Michael ms at cerenity.org
Fri Nov 17 19:43:46 EST 2006


The reason for your problem is that, at it's core, it's not threadsafe - you
have a shared data value that 2 distinct things are trying to update.

Mythmon at gmail.com wrote:
...
> from threading import Thread

The following value is updated by 2 things trying to run in parallel.

> c = Console.getconsole()

The following value is used for communications.

> turn = 0


> class timer (Thread):
>     def run ( self ):
>         global turn
....
>         go = True
>         while go:
>             newTime = time.time()
>             timeLeft[turn] -= newTime - oldTime[turn]
>             oldTime[turn] = newTime

value that "turn" relates to here is merely read. On the surface this looks
safe, but if turn changes in value between any of these 3 "reads" of the
value of "turn", then it will go wrong.

>             c.text(3,3,minutes[0] + ':' + seconds[0])
>             c.text(12,3,minutes[1] + ':' + seconds[1])

c gets updated here.

> class eventMonitor (Thread):
>     def run ( self ):
>         global turn

Shared data, but this is the only writer to this value.

>         go = True
>         while go:
>             event = c.peek()
>             if event != None:
>                 c.get()
... (turn gets updated here as well, which can cause a race hazard in timer) 
>                     c.text(10,20,'1')

c effectively changes in value here as well.

> timer().start()
> eventMonitor().start()

Now you *could* introduce locks into this if you wanted. The alternative
approach is to recognise that the real reason you're hitting problems is
because you're using shared data, and that an alternative is to send
messages between the things you're using.

ie collect events from the keyboard, 
   send them to something handling the logic, 
   have that send a message (as you essentially do with "turn" above) to the
     timer logic,
   and then have that send a message to the display.

That way, since you have explicit hand off of data you don't run the risk of
your code crashing. (you also avoid a few other classes of problems :)

The way this would work with Kamaelia (given I've just posted elsewhere in
the thread about that) is you could build a simple pipeline as follows:

Pipeline( KeyEvent(key_events = { pygame.K_SPACE: ("SPACE", "outbox")} ),
          ChessTurnLogic(),
          TimerLogic(),
          Ticker(background_colour=(128,48,128),
                 render_left = 1,
                 render_top = 1,
                 render_right = 600,
                 render_bottom = 200,
                 position = (100, 300),
          )
).run()

This uses pygame for the display instead of the console which you're using.
It takes keypresses and turns them into a message to trigger the chess turn
logic to emit whose turn it is.

This then feeds into somethin that manages the timer logic - which in this
case emits a message (to be displayed by the ticker) about whose just
finished their turn, and how much time they spent on their turn.

KeyEvent and Ticker are pre-existing components. 

The ChessTurnLogic looks like this:

class ChessTurnLogic(Axon.Component.component):
   def main(self):
       myturn, notmyturn = "white", "black"
       self.send(myturn, "outbox")
       while 1:
           while self.dataReady("inbox"):
               self.recv("inbox")
               myturn, notmyturn = notmyturn, myturn
               self.send(myturn, "outbox")
           if not self.anyReady():
               self.pause()
           yield 1


The Timer Logic looks like this:

class TimerLogic(Axon.Component.component):
    def main(self):
        times_info = {}
        player = None
        while 1:
           while self.dataReady("inbox"):
               new_player = self.recv("inbox")
               now = time.time()

               if player is not None:
                  (total, last) = times_info[player]
                  total = total + (now - last)
                  times_info[player] = (total, now)
                  self.send(player + " " + str(total) + "\n\n", "outbox")

               player = new_player
               try:
                  (total, last) = times_info[player]
                  times_info[player] = (total, now)
               except KeyError:
                  times_info[player] = (0, now)

           if not self.anyReady():
              self.pause()

           yield 1

There's probably shorter ways of dealing with the timer logic, but it'll
also work for a draughts (checkers) logic component:

class DraughtsTurnLogic(Axon.Component.component):
   def main(self):
       myturn, notmyturn = "black", "white"
       self.send(myturn, "outbox")
       while 1:
           while self.dataReady("inbox"):
               self.recv("inbox")
               myturn, notmyturn = notmyturn, myturn
               self.send(myturn, "outbox")
           if not self.anyReady():
               self.pause()
           yield 1

Or indeed a game where there are more than 2 players. (Which is a nice
sideeffect of decoupling your code like this)

The full thing including imports looks like this for reference:

#!/usr/bin/python

import Axon
import time
import pygame
from Kamaelia.Chassis.Pipeline import Pipeline
from Kamaelia.UI.Pygame.Ticker import Ticker
from Kamaelia.UI.Pygame.KeyEvent import KeyEvent

class ChessTurnLogic(Axon.Component.component):
   def main(self):
       myturn, notmyturn = "white", "black"
       self.send(myturn, "outbox")
       while 1:
           while self.dataReady("inbox"):
               self.recv("inbox")
               myturn, notmyturn = notmyturn, myturn
               self.send(myturn, "outbox")
           if not self.anyReady():
               self.pause()
           yield 1

class TimerLogic(Axon.Component.component):
    def main(self):
        times_info = {}
        player = None
        while 1:
           while self.dataReady("inbox"):
               new_player = self.recv("inbox")
               now = time.time()

               if player is not None:
                  (total, last) = times_info[player]
                  total = total + (now - last)
                  times_info[player] = (total, now)
                  self.send(player + " " + str(total) + "\n\n", "outbox")

               player = new_player
               try:
                  (total, last) = times_info[player]
                  times_info[player] = (total, now)
               except KeyError:
                  times_info[player] = (0, now)

           if not self.anyReady():
              self.pause()

           yield 1

Pipeline( KeyEvent(key_events = { pygame.K_SPACE: ("SPACE", "outbox")} ),
          ChessTurnLogic(),
          TimerLogic(),
          Ticker(background_colour=(128,48,128),
                 render_left = 1,
                 render_top = 1,
                 render_right = 600,
                 render_bottom = 200,
                 position = (100, 300),
          )
).run()

(I've tested the above code BTW. It's not pretty, but it works :).
 Prettiness could be added in all sorts of ways though :-)


Regards,


Michael.
--
Kamaelia Project Lead/Dust Puppy
http://kamaelia.sourceforge.net/Home
http://yeoldeclue.com/blog








More information about the Python-list mailing list