Synchronous Programming Model of asynchronous operations based on python yield mechanism, pythonyield

Source: Internet
Author: User

Synchronous Programming Model of asynchronous operations based on python yield mechanism, pythonyield

This article summarizes how to synchronize and simulate asynchronous operations when writing python code to improve code readability and scalability.

Generally, the game engine uses a distributed framework to balance the resource loads of Server clusters through certain policies, so as to ensure high concurrency and high CPU utilization of server operations, and ultimately improve the Game Performance and load. Because the logic layer calls of the engine are non-preemptible and communication between servers is performed asynchronously, the game logic cannot be executed synchronously. Therefore, many callback functions must be manually added to the Code layer, distributes a complete function in various callback functions in a fragmented manner.

Asynchronous Logic

Take the replica scoring logic in the game as an example. At the end of the replica, the replica management process needs to collect the combat information of each player in the replica, and finally provide a replica score based on the statistics in the management process, reward. Because each player entity is randomly distributed in different processes, the management process needs to obtain the combat information of the player through asynchronous calls.

The implementation code is as follows:

#-*-Coding: gbk-*-import random # Player entity class Player (object): def _ init _ (self, entityId): super (Player, self ). _ init _ () # player ID self. entityId = entityId def onFubenEnd (self, mailBox): score = random. randint (1, 10) print "onFubenEnd player % d score % d" % (self. entityId, score) # Send your id and Combat Information mailBox to the replica management process. onEvalFubenScore (self. entityId, score) # copy management class FubenStub (object): def _ init _ (self, players): super (FubenStub, self ). _ init _ () self. players = players def evalFubenScore (self): self. playerRelayCnt = 0 self. totalScore = 0 # notify each registered player that the copy has ended and the combat information is requested for player in self. players: player. onFubenEnd (self) def onEvalFubenScore (self, entityId, score): # receives the Combat Information of one of the players print "onEvalFubenScore player % d score % d" % (entityId, score) self. playerRelayCnt + = 1 self. totalScore + = score # if len (self. players) = self. playerRelayCnt: print 'the fuben totalScore is % d' % self. totalScore if _ name _ = '_ main _': # simulate Player entity creation players = [Player (I) for I in xrange (3)] # When a copy starts, each player registers its own MailBox to the copy management process fs = FubenStub (players) # The copy is in progress #.... # When the copy ends, the score starts to fs. evalFubenScore ()

The Code simplifies the implementation of the replica scoring logic. The Player class represents the Player entity of the game, seamlessly switches between different servers during game operation, and FubenStub represents the replica management process, at the beginning of the copy, all players in the copy will register their MailBox in the management process. MailBox indicates the remote call handle of each entity. At the end of the copy, FubenStub first sends the copy End message to each player and requests the player's combat information. After receiving the message, the player sends the combat information to FubenStub; then, after FubenStub collects information about all players, the score is printed.

Synchronization Logic

If Player and FubenStub are in the same process, all operations can be synchronized. When FubenStub sends a copy of the End message to the Player, it can immediately obtain the combat information of the Player, the implementation code is as follows:

# -*- coding: gbk -*- import random class Player(object):  def __init__(self, entityId):    super(Player, self).__init__()    self.entityId = entityId   def onFubenEnd(self, mailBox):    score = random.randint(1, 10)    print "onFubenEnd player %d score %d"%(self.entityId, score)    return self.entityId, score class FubenStub(object):  def __init__(self, players):    super(FubenStub, self).__init__()    self.players = players   def evalFubenScore(self):    totalScore = 0    for player in self.players:      entityId, score = player.onFubenEnd(self)      print "onEvalFubenScore player %d score %d"%(entityId, score)      totalScore += score     print 'The fuben totalScore is %d'%totalScore if __name__ == '__main__':  players = [Player(i) for i in xrange(3)]   fs = FubenStub(players)  fs.evalFubenScore()

From the above two pieces of code, we can see that due to asynchronous operations, the scoring logic in FubenStub is artificially divided into two functional points: 1) send a copy to the player to end the message; 2) Accept the player's combat information; and the two function points are distributed in two different functions. If the game logic is complex, it will inevitably lead to scattered functions and excessive onXXX asynchronous callback functions, resulting in improved code development and maintenance costs and reduced readability and scalability.

If there is a way to temporarily suspend the function during asynchronous calls and resume execution after the callback function returns the returned value, you can use synchronous programming mode to develop asynchronous logic.

Yield keywords

Yield is a keyword in Python. If the yield keyword appears in the function body, Python will change the context of the entire function. Calling this function will not return a value, but a generator object. The generator object can be executed only when the next iteration function of the generator is called. When the generator object is executed with the yield expression, the function will be suspended temporarily and wait for the next call to resume execution, the specific mechanism is as follows:

1) call the next method of the generator object to start function execution;

2) When the generator object is executed to contain the yield expression, the function is suspended;

3) The next function call will drive the generator object to continue executing subsequent statements until the next yield is suspended again;

4) if a next call drives the generator to continue execution, and then the function ends normally, the generator will throw a StopIteration exception;

The following code is used:

def f():  print "Before first yield"  yield 1  print "Before second yield"  yield 2  print "After second yield" g = f()print "Before first next"g.next()print "Before second next"g.next()print "Before third yield"g.next()

The execution result is:

Before first next

Before first yield

Before second next

Before second yield

Before third yield

After second yield

StopIteration

Ha, with the Mechanism to temporarily suspend the function, the question of how to pass the return value of asynchronous calls is left. In fact, the next function of the generator has implemented a mechanism for passing parameters from inside the generator object, and python also provides a mechanism for sending parameters from inside the external generator object, the specific mechanism is as follows:

1) when the next function is called to drive the generator, next will wait for the next yield suspension in the generator and return the parameters following the yield to next;

2) When passing parameters to the generator, you must replace the next function with the send function. In this case, the send function is the same as the next function (the driver generator executes and waits for the return value ), at the same time, send will pass the following parameters to the yield suspended before the generator;

The following code is used:

def f():  msg = yield 'first yield msg'  print "generator inner receive:", msg  msg = yield 'second yield msg'  print "generator inner receive:", msg g = f()msg = g.next()print "generator outer receive:", msgmsg = g.send('first send msg')print "generator outer receive:", msgg.send('second send msg')

The execution result is:

Generator outer receive: first yield msg

Generator inner receive: first send msg

Generator outer receive: second yield msg

Generator inner receive: second send msg

StopIteration

Synchronous implementation

Well, everything is ready for development. The following is a simple project encapsulation of the yield mechanism for future development. The following code provides an interface named IFakeSyncCall. All logical classes containing asynchronous operations can inherit this interface:

class IFakeSyncCall(object):  def __init__(self):    super(IFakeSyncCall, self).__init__()    self.generators = {}   @staticmethod  def FAKE_SYNCALL():    def fwrap(method):      def fakeSyncCall(instance, *args, **kwargs):        instance.generators[method.__name__] = method(instance, *args, **kwargs)        func, args = instance.generators[method.__name__].next()        func(*args)      return fakeSyncCall    return fwrap   def onFakeSyncCall(self, identify, result):    try:      func, args = self.generators[identify].send(result)      func(*args)    except StopIteration:      self.generators.pop(identify)

The generators attribute in the interface is used to save the generator objects that have started execution in the class. The FAKE_SYNCALL function is a decorator. The decoration class contains the yield function and changes the call context of the function, the next call to the generator object is encapsulated in fakeSyncCall; The onFakeSyncCall function encapsulates the logic of all onXXX functions, and other entities pass the asynchronous callback return value by calling this function.

The following is the logic code of asynchronous copy scoring after synchronization improvement:

# -*- coding: gbk -*-import random class Player(object):  def __init__(self, entityId):    super(Player, self).__init__()    self.entityId = entityId   def onFubenEnd(self, mailBox):    score = random.randint(1, 10)    print "onFubenEnd player %d score %d"%(self.entityId, score)    mailBox.onFakeSyncCall('evalFubenScore', (self.entityId, score)) class FubenStub(IFakeSyncCall):  def __init__(self, players):    super(FubenStub, self).__init__()    self.players = players   @IFakeSyncCall.FAKE_SYNCALL()  def evalFubenScore(self):    totalScore = 0    for player in self.players:      entityId, score = yield (player.onFubenEnd, (self,))      print "onEvalFubenScore player %d score %d"%(entityId, score)      totalScore += score     print 'the totalScore is %d'%totalScore if __name__ == '__main__':  players = [Player(i) for i in xrange(3)]   fs = FubenStub(players)  fs.evalFubenScore()

Compared with the evalFubenScore function, it is almost the same as the original synchronization logic code.

Another advantage of implementing the Synchronous Programming Model Using the yield mechanism is that it can ensure the logic serialization of all asynchronous calls, thus ensuring data consistency and effectiveness, in particular, the traditional timer sleep mechanism can be abandoned in various asynchronous initialization processes to eliminate some hidden bugs caused by data inconsistency from the source.

Articles you may be interested in:
  • Python in-depth understanding of yield
  • The tool for automatic synchronization of computer time written in Python
  • Python yield usage example
  • How to use yield in python
  • Python asynchronous task queue example
  • Test the performance of asynchronous Socket programming in Python
  • Python multi-thread synchronization Lock, RLock, Semaphore, Event instance
  • Generator and yield in Python
  • Simplify complex programming models with Python SimPy Library

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.