File: async1.py

File: async1.py

#!/usr/bin/python
"""
================================================================================
Coroutines 101: portable Python 3.X and 2.X flavor. (Copyright M. Lutz, 2015)
Demonstrate nonpreemptive, asynchronous multitasking, with a simple event loop
that switches between generator functions.
This model is cooperative: tasks must voluntarily and manually yield control 
to an event loop, and be short enough so as to not monopolize the CPU. Hence, 
this requires a rigid and perhaps unnatural code structure, and is better used
for programs that can be designed as a set of tasks that run in quick bursts.
By contrast, threads and processes offer much more generalized preemptive 
multitasking, which shares the CPU among normally-structured tasks without
requiring code to manually yield control. But they may also require use of 
synchronization tools such as thread queues, sockets, or pipes, as the tasks
may overlap arbitrarily (see Programming Python). Both models can be used to 
interleave work over time, and avoid blocking while a program waits for IO or
other non-CPU operations, even when they don't boost overall program speed.
The simple generators here are adequate for task switching, but reflect only 
the first level of this volatile and obscure corner of the Python language:
-In 2.3+: original model - yield (plus expressions in 2.4)
-In 2.5+: sends (plus throws, closes)
-In 3.3+: returns and subgenerators
-In 3.5+: asynch/await (plus asyncio in 3.4)
 
For examples of most of the above: http://learning-python.com/books/gendemo.py
================================================================================
"""
from __future__ import print_function # 2.X
import time, sys
def itime(): return '@%d' % round(time.clock())
#
# Event loop
#
def switcher(*tasks):
 taskqueue = [task() for task in tasks] # Start all generators
 while taskqueue: # While any running tasks remain
 front = taskqueue.pop(0) # Fetch next task at front
 try:
 result = next(front) # Resume task, run to its next yield
 except StopIteration:
 pass # Returned: task finished
 else:
 name = front.__name__ # Yielded: reschedule at end of queue
 print(name, result, itime())
 taskqueue.append(front)
# 
# Tasks
# 
def task1():
 for i in range(6): # Resumed here by next() in switcher (or for)
 time.sleep(1) # Simulate a nonpreemptable action here
 yield i # Suspend and yield control back to switcher (or for)
 # Removed from queue on return (or exit for)
def task2():
 for i in range(3):
 time.sleep(2)
 yield i
def task3():
 for i in range(2):
 time.sleep(3)
 yield i
#
# Task launcher
#
if __name__ == '__main__':
 if len(sys.argv) > 1:
 # 
 # SERIAL: with command-line arg, run the functions by themselves.
 # Each task checks in at regular intervals and runs atomically.
 #
 start = time.clock()
 for i in task1(): print('task1', i, itime()) 
 for i in task2(): print('task2', i, itime())
 for i in task3(): print('task3', i, itime())
 print('all tasks finished, total time: %.2f' % (time.clock() - start))
 
 else:
 # 
 # ASYNCHRONOUS: without arg, run the functions together, overlapped.
 # Tasks check in at irregular intervals and run interleaved. 
 # 
 start = time.clock() 
 switcher(task1, task2, task3)
 print('all tasks finished, total time: %.2f' % (time.clock() - start))
"""
================================================================================
Expected output, under both 3.X and 2.X:
C:\Code> async1.py 1
task1 0 @1
task1 1 @2
task1 2 @3
task1 3 @4
task1 4 @5
task1 5 @6
task2 0 @8
task2 1 @10
task2 2 @12
task3 0 @15
task3 1 @18
all tasks finished, total time: 18.13
C:\Code> async1.py
task1 0 @1
task2 0 @3
task3 0 @6
task1 1 @7
task2 1 @9
task3 1 @12
task1 2 @13
task2 2 @15
task1 3 @16
task1 4 @17
task1 5 @18
all tasks finished, total time: 18.13
================================================================================
"""



AltStyle によって変換されたページ (->オリジナル) /