我有一个需要几个小时才能处理的串行Python应用程序,如何减少运行所需的时间?

时间:2010-06-25 01:05:32

标签: python

有人可以发一些多线程python的例子吗?我在互联网上搜索,但找不到一个简单,易于复制的教程。简单的例子很好。

我已经编写了一个程序,需要花费几个小时来连续运行 - 我希望我可以在多线程之后将它的运行时间缩短到几分钟。

12 个答案:

答案 0 :(得分:6)

我看到你有很多例子,都是@Noctis,但我不确定他们会如何帮助你。更直接地解决您的问题:在今天的CPython中,多线程可以加速您的应用程序的唯一方法是,如果您的减速很大程度上归功于“阻止I / O”操作,例如:由于与(例如)数据库服务器,邮件服务器,网站等的交互。 (加速I / O的一个强大的替代方法是异步,AKA事件驱动,编程,最丰富的Python框架是twisted - 但如果你从未做过事件,它可能更难学习 - 驱动编码)。

即使你的机器中有很多内核,一个多线程Python进程一次只能使用其中一个,除非它正在执行特殊编码的扩展(通常用C,C ++,Cython等)。在可行的情况下“释放GIL”(全球翻译锁)。

如果你有很多内核,multiprocessing(一个模块的界面设计看起来很像threading可以确实加速你的计划。还有许多其他软件包支持“对称多处理器”分布式编程,请参阅列表here,但是,在所有这些软件包中,multiprocessing是标准库中的一部分(很方便的事)。如果您有多台计算机之间有快速LAN,您还应该考虑distributed处理的更通用的方法,这可以让您使用所有可用的计算机执行相同的任务(其中一些包也列出在我之前提供的URL中,在“cluster computing”标题下。)

对于任何数量的可用内核或计算机,您可以获得的最大速度取决于您的问题的性质 - 如果问题本身适合它,那么您也可以使用算法和数据结构。重新使用...并非所有都能加速(它在“令人难以置信的并行”问题之间变化,例如光线跟踪,一直线性加速)到“本质上串行”的问题,其中100台机器不会更快比一个)。因此,如果不了解问题的性质,很难再建议你;小心解释一下?

答案 1 :(得分:2)

这不是你问题的直接答案,但是:

您是否考虑过使用Python multiprocessing module?它通过分支新进程来工作,这些进程的开销略高,但通常可以更快,因为它避免了Python全局解释器锁的争用问题。文档非常详尽,还有很多关于它的在线文章。

答案 2 :(得分:1)

Here是一个很好的教程。第3.1.2节(本教程的第72页)有一个使用线程的简单客户端/服务器示例。

答案 3 :(得分:1)

Python的C(CPython)实现是多线程的,但 NOT 并发。由于Global Interpeter Lock(GIL),一次只运行一个线程。如果您想要真正的并发,可以使用mulitprocessing模块。

所发布的所有示例都不会帮助您的多小时流程缩短,实际上它们会导致它运行L O N G E R.

此外,您没有提到您实际在做什么,但如果您正在读取/写入任何数据(网络或磁盘),则可能是I / O限制。如果是这种情况,并发性只会加剧问题。

答案 4 :(得分:0)

示例1

import thread

class sync:

    def __init__(self, threads):
        self.__threads = threads
        self.__count = 0
        self.__main = thread.allocate_lock()
        self.__exit = thread.allocate_lock()
        self.__exit.acquire()

    def sync(self):
        self.__main.acquire()
        self.__count += 1
        if self.__count < self.__threads:
            self.__main.release()
        else:
            self.__exit.release()
        self.__exit.acquire()
        self.__count -= 1
        if self.__count > 0:
            self.__exit.release()
        else:
            self.__main.release()

def example():
    def get_input(share):
        while share[0]:
            share[1] = raw_input('Please say something.\n')
            share[2].sync()
        share[3].sync()
    def do_output(share):
        while share[0]:
            share[2].sync()
            print 'You said, "%s"' % share[1]
        share[3].sync()
    share = [True, None, sync(2), sync(3)]
    thread.start_new_thread(get_input, (share,))
    thread.start_new_thread(do_output, (share,))
    import time; time.sleep(60)
    share[0] = False
    share[3].sync()

if __name__ == '__main__':
    example()

答案 5 :(得分:0)

示例2

from os.path import basename
from Queue import Queue
from random import random
from sys import argv, exit
from threading import Thread
from time import sleep

# for creating widgets
class Widget:
    pass

# for creating stacks
class Stack:
    def __init__(self):
        self.__stack = list()
    def __len__(self):
        return len(self.__stack)
    def push(self, item):
        self.__stack.append(item)
    def pop(self):
        return self.__stack.pop()

# provides an outline for the execution of the program
def main():
    # check and parse the command line arguments
    parse_sys_argv()
    # setup the variables used by the threads
    run_flag = [True]
    queue = Queue(argv[1])
    send = Stack()
    recv = Stack()
    # start the threads
    producer = Thread(target=produce, args=(run_flag, queue, send))
    consumer = Thread(target=consume, args=(run_flag, queue, recv, producer))
    producer.start()
    consumer.start()
    # let the threads do their work
    sleep(argv[2])
    run_flag[0] = False
    consumer.join()
    # verify that the solution was valid
    calculate_results(send, recv)

# parses and checks the command line arguments
def parse_sys_argv():
    try:
        # there should be two command line arguments
        assert len(argv) == 3
        # convert <buf_size> and check
        argv[1] = abs(int(argv[1]))
        assert argv[1] > 0
        # convert <run_time> and check
        argv[2] = abs(float(argv[2]))
        assert argv[2] > 0
    except:
        # print out usage information
        print basename(argv[0]),
        print '<buf_size> <run_time>'
        # exits the program
        exit(1)

# called by the producer thread
def produce(run_flag, queue, send):
    while run_flag[0]:
        # simulate production
        sleep(random())
        # put widget in buffer
        item = Widget()
        queue.put(item)
        send.push(item)

# called by the consumer thread
def consume(run_flag, queue, recv, producer):
    # consume items while running
    while run_flag[0]:
        do_consume(queue, recv)
    # empty the queue to allow maximum room
    while not queue.empty():
        do_consume(queue, recv)
    # wait for the producer to end
    producer.join()
    # consume any other items that might have been produced
    while not queue.empty():
        do_consume(queue, recv)

# executes one consumption operation
def do_consume(queue, recv):
    # get a widget from the queue
    recv.push(queue.get())
    # simulate consumption
    sleep(random())

# verifies that send and recv were equal
def calculate_results(send, recv):
    print 'Solution has',
    try:
        # make sure that send and recv have the same length
        assert len(send) == len(recv)
        # check all of the contents of send and recv
        while send:
            # check the identity of the items in send and recv
            assert send.pop() is recv.pop()
        print 'passed.'
    except:
        print 'failed.'

# starts the program
if __name__ == '__main__':
    main()

答案 6 :(得分:0)

示例3

from os.path import basename
from Queue import Queue
from random import random, seed
from sys import argv, exit
from threading import Thread
from time import sleep

################################################################################

class Widget:
    pass

class Stack:
    def __init__(self):
        self.__stack = list()
    def __len__(self):
        return len(self.__stack)
    def push(self, item):
        self.__stack.append(item)
    def pop(self):
        return self.__stack.pop()

################################################################################

def main():
    parse_argv()
    run_flag, buffer_queue, producer_stack, consumer_stack, print_queue = [True], Queue(argv[1]), Stack(), Stack(), Queue()
    producer_thread = Thread(target=producer, args=(run_flag, argv[3], buffer_queue, producer_stack, print_queue))
    consumer_thread = Thread(target=consumer, args=(run_flag, producer_thread, buffer_queue, consumer_stack, argv[4], print_queue))
    printer_thread = Thread(target=printer, args=(run_flag, consumer_thread, print_queue))
    producer_thread.start()
    consumer_thread.start()
    printer_thread.start()
    sleep(argv[2])
    run_flag[0] = False
    printer_thread.join()
    check_results(producer_stack , consumer_stack)

def parse_argv():
    try:
        assert len(argv) > 4
        argv[1] = abs(int(argv[1]))
        argv[2] = abs(float(argv[2]))
        assert argv[1] and argv[2]
        argv[3] = abs(float(argv[3]))
        argv[4] = abs(float(argv[4]))
        if len(argv) > 5:
            seed(convert(' '.join(argv[5:])))
    except:
        print basename(argv[0]), '<buff_size> <main_time> <prod_time> <cons_time> [<seed>]'
        exit(1)

def convert(string):
    number = 1
    for character in string:
        number <<= 8
        number += ord(character)
    return number

def check_results(producer_stack , consumer_stack):
    print 'Solution has',
    try:
        assert len(producer_stack) == len(consumer_stack)
        while producer_stack:
            assert producer_stack.pop() is consumer_stack.pop()
        print 'passed.'
    except:
        print 'failed.'

################################################################################

def producer(run_flag, max_time, buffer_queue, producer_stack, print_queue):
    while run_flag[0]:
        sleep(random() * max_time)
        widget = Widget()
        buffer_queue.put(widget)
        producer_stack.push(widget)
        print_queue.put('Producer: %s Widget' % id(widget))

def consumer(run_flag, producer_thread, buffer_queue, consumer_stack, max_time, print_queue):
    while run_flag[0] or producer_thread.isAlive() or not buffer_queue.empty():
        widget = buffer_queue.get()
        consumer_stack.push(widget)
        sleep(random() * max_time)
        print_queue.put('Consumer: %s Widget' % id(widget))

def printer(run_flag, consumer_thread, print_queue):
    while run_flag[0] or consumer_thread.isAlive() or not print_queue.empty():
        if print_queue.empty():
            sleep(0.1)
        else:
            print print_queue.get()

################################################################################

if __name__ == '__main__':
    main()

答案 7 :(得分:0)

示例4

import socket
import sys
import thread

def main(setup, error):
    sys.stderr = file(error, 'a')
    for settings in parse(setup):
        thread.start_new_thread(server, settings)
    lock = thread.allocate_lock()
    lock.acquire()
    lock.acquire()

def parse(setup):
    settings = list()
    for line in file(setup):
        parts = line.split()
        settings.append((parts[0], int(parts[1]), int(parts[2])))
    return settings

def server(*settings):
    try:
        dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        dock_socket.bind(('', settings[2]))
        dock_socket.listen(5)
        while True:
            client_socket = dock_socket.accept()[0]
            server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server_socket.connect((settings[0], settings[1]))
            thread.start_new_thread(forward, (client_socket, server_socket))
            thread.start_new_thread(forward, (server_socket, client_socket))
    finally:
        thread.start_new_thread(server, settings)

def forward(source, destination):
    string = ' '
    while string:
        string = source.recv(1024)
        if string:
            destination.sendall(string)
        else:
            source.shutdown(socket.SHUT_RD)
            destination.shutdown(socket.SHUT_WR)

if __name__ == '__main__':
    main('proxy.ini', 'error.log')

答案 8 :(得分:0)

示例5

# #include <windows.h>
import thread
# #include <math.h>
import math
# #include <stdio.h>
import sys
# #include <stdlib.h>
import time

# static int runFlag = TRUE;
runFlag = True

# void main(int argc, char *argv[]) {
def main(argc, argv):
    global runFlag
    # unsigned int runTime
    # PYTHON: NO CODE

    # SYSTEMTIME now;
    # PYTHON: NO CODE
    # WORD stopTimeMinute, stopTimeSecond;
    # PYTHON: NO CODE

    # // Get command line argument, N
    try:
        N = abs(int(argv[1]))
    except:
        sys.exit(1)
    # // Get the time the threads should run, runtime
    try:
        runTime = abs(int(argv[2]))
    except:
        sys.exit(1)
    # // Calculate time to halt (learn better ways to do this later)
    # GetSystemTime(&now);
    now = time.localtime()
    # printf("mthread: Suite starting at system time
    #   %d:%d:%d\n", now.wHour, now.wMinute, now.wSecond);
    sys.stdout.write('mthread: Suite starting at system time %d:%d:%d\n' \
          % (now.tm_hour, now.tm_min, now.tm_sec))
    # stopTimeSecond = (now.wSecond + (WORD) runTime) % 60;
    stopTimeSecond = (now.tm_sec + runTime) % 60
    # stopTimeMinute = now.wMinute + (now.wSecond +
    #   (WORD) runTime) / 60;
    stopTimeMinute = now.tm_min + (now.tm_sec + runTime) / 60

    # // For 1 to N
    # for (i = 0; i < N; i++) {
    for i in range(N):
        # // Create a new thread to execute simulated word
        thread.start_new_thread(threadWork, ())
        # Sleep(100);               // Let newly created thread run
        time.sleep(0.1)
    # }
    # PYTHON: NO CODE

    # // Cycle while children work ...
    # while (runFlag) {
    while runFlag:
        # GetSystemTime(&now);
        now = time.localtime()
        # if ((now.wMinute >= stopTimeMinute)
        #     &&
        #     (now.wSecond >= stopTimeSecond)
        #    )
        if now.tm_min >= stopTimeMinute \
           and now.tm_sec >= stopTimeSecond:
            # runFlag = FALSE;
            runFlag = False
        # Sleep(1000);
        time.sleep(1)
    # }
    # PYTHON: NO CODE
    # Sleep(5000);
    time.sleep(5)
# }
# PYTHON: NO CODE

# // The code executed by each worker thread (simulated work)
# DWORD WINAPI threadWork(LPVOID threadNo) {
def threadWork():
    threadNo = thread.get_ident()
    # // Local variables
    # double y;
    # PYTHON: NO CODE
    # const double x = 3.14159;
    x = 3.14159
    # const double e = 2.7183;
    e = 2.7183
    # int i;
    # PYTHON: NO CODE
    # const int napTime = 1000;             // in milliseconds
    napTime = 1000
    # const int busyTime = 40000;
    busyTime = 40000
    # DWORD result = 0;
    result = 0

    # // Create load
    # while (runFlag) {
    while runFlag:
        # // Parameterized processor burst phase
        # for (i = 0; i < busyTime; i++)
        for i in range(busyTime):
            # y = pow(x, e);
            y = math.pow(x, e)
        # // Parameterized sleep phase
        # Sleep(napTime);
        time.sleep(napTime / 1000.0)
        # // Write message to stdout
        sys.stdout.write('Thread %s just woke up.\n' % threadNo)
    # }
    # PYTHON: NO CODE
    # // Terminating
    # return result;
    return result
# }
# PYTHON: NO CODE

if __name__ == '__main__':
    main(len(sys.argv), sys.argv)

答案 9 :(得分:0)

示例6

import tkinter
import _thread
import time

EPOCH_DELTA = 946684800
MICREV_IN_DAY = 1000000
MILREV_IN_DAY = 1000

SECOND_IN_DAY = 86400
DAY_IN_WEEK = 7
WEEK_IN_MONTH = 4
MONTH_IN_SEASON = 3
SEASON_IN_YEAR = 4

SECOND_IN_WEEK = SECOND_IN_DAY * DAY_IN_WEEK
SECOND_IN_MONTH = SECOND_IN_WEEK * WEEK_IN_MONTH
SECOND_IN_SEASON = SECOND_IN_MONTH * MONTH_IN_SEASON
SECOND_IN_YEAR = SECOND_IN_SEASON * SEASON_IN_YEAR

def seconds():
    "Return seconds since the epoch."
    return time.time() - EPOCH_DELTA

def micrev(seconds):
    "Convert from seconds to micrev."
    x = seconds % SECOND_IN_DAY * MICREV_IN_DAY / SECOND_IN_DAY % MILREV_IN_DAY
    return int(x)

def milrev(seconds):
    "Convert from seconds to milrev."
    x = seconds % SECOND_IN_DAY * MILREV_IN_DAY / SECOND_IN_DAY
    return int(x)

def day(seconds):
    "Convert from seconds to days."
    x = seconds / SECOND_IN_DAY % DAY_IN_WEEK
    return int(x)

def week(seconds):
    "Convert from seconds to weeks."
    x = seconds / SECOND_IN_WEEK % WEEK_IN_MONTH
    return int(x)

def month(seconds):
    "Convert from seconds to months."
    x = seconds / SECOND_IN_MONTH % MONTH_IN_SEASON
    return int(x)

def season(seconds):
    "Convert from seconds to seasons."
    x = seconds / SECOND_IN_SEASON % SEASON_IN_YEAR
    return int(x)

def year(seconds):
    "Convert from seconds to years."
    x = seconds / SECOND_IN_YEAR
    return int(x)

UNITS = year, season, month, week, day, milrev, micrev

def text(seconds, spec='{0}.{1}.{2}.{3}.{4}.{5:03}.{6:03}', unit=UNITS):
    "Convert from seconds to text."
    return spec.format(*[func(seconds) for func in unit])

class Quantum_Timer:

    "Quantum_Timer(function, *args, **kwargs) -> Quantum_Timer"

    def __init__(self, function, *args, **kwargs):
        "Initialize the Quantum_Timer object."
        self.__function = function
        self.__args = args
        self.__kwargs = kwargs
        self.__thread = False
        self.__lock = _thread.allocate_lock()

    def start(self):
        "Start the Quantum_Timer object."
        with self.__lock:
            self.__active = True
            if not self.__thread:
                self.__thread = True
                _thread.start_new_thread(self.__run, ())

    def stop(self):
        "Stop the Quantum_Timer object."
        with self.__lock:
            self.__active = False

    def __run(self):
        "Private class method."
        while True:
            secs = time.clock()
            plus = secs + 0.0864
            over = plus % 0.0864
            diff = plus - secs - over
            time.sleep(diff)
            with self.__lock:
                if not self.__active:
                    self.__thread = False
                    break
            self.__function(*self.__args, **self.__kwargs)

def main():
    root = tkinter.Tk()
    root.resizable(False, False)
    root.title('Time in Tessaressunago')
    secs = tkinter.StringVar()
    text = tkinter.Label(textvariable=secs, font=('helvetica', 16, 'bold'))
    text.grid(padx=5, pady=5)
    thread = Quantum_Timer(update, secs)
    thread.start()
    root.mainloop()

def update(secs):
    s = seconds()
    t = text(s)
    p = 1000000000 * 1.01 ** (s / SECOND_IN_YEAR)
    secs.set('Time = {0}\nNational = {1}'.format(t, fix(p)))

def fix(number, sep=','):
    number = str(int(number))
    string = ''
    while number:
        string = number[-1] + string
        number = number[:-1]
        if number and not (len(string) + 1) % 4:
            string = sep + string
    return string

if __name__ == '__main__':
    main()

答案 10 :(得分:0)

示例7

HOST = '127.0.0.1'
PORT = 8080

from Tkinter import *
import tkColorChooser

import socket
import thread
import cPickle

################################################################################

class ZSP:

    'ZSP(socket) -> ZSP'

    def __init__(self, socket):
        'Initialize the Zero SPOTS Protocol object.'
        self.__file = socket.makefile('b', 0)

    def send(self, obj):
        'Send one object.'
        cPickle.dump(obj, self.__file, cPickle.HIGHEST_PROTOCOL)

    def recv(self):
        'Receive one object.'
        return cPickle.load(self.__file)

################################################################################

def main():
    global hold, fill, draw, look
    hold = []
    fill = '#000000'
    connect()
    root = Tk()
    root.title('Paint 2.0')
    root.resizable(False, False)
    upper = LabelFrame(root, text='Your Canvas')
    lower = LabelFrame(root, text='Their Canvas')
    draw = Canvas(upper, bg='#ffffff', width=400, height=300, highlightthickness=0)
    look = Canvas(lower, bg='#ffffff', width=400, height=300, highlightthickness=0)
    cursor = Button(upper, text='Cursor Color', command=change_cursor)
    canvas = Button(upper, text='Canvas Color', command=change_canvas)
    draw.bind('<Motion>', motion)
    draw.bind('<ButtonPress-1>', press)
    draw.bind('<ButtonRelease-1>', release)
    draw.bind('<Button-3>', delete)
    upper.grid(padx=5, pady=5)
    lower.grid(padx=5, pady=5)
    draw.grid(row=0, column=0, padx=5, pady=5, columnspan=2)
    look.grid(padx=5, pady=5)
    cursor.grid(row=1, column=0, padx=5, pady=5, sticky=EW)
    canvas.grid(row=1, column=1, padx=5, pady=5, sticky=EW)
    root.mainloop()

################################################################################

def connect():
    try:
        start_client()
    except:
        start_server()
    thread.start_new_thread(processor, ())

def start_client():
    global ZSP
    server = socket.socket()
    server.connect((HOST, PORT))
    ZSP = ZSP(server)

def start_server():
    global ZSP
    server = socket.socket()
    server.bind(('', PORT))
    server.listen(1)
    ZSP = ZSP(server.accept()[0])

def processor():
    while True:
        func, args, kwargs = ZSP.recv()
        getattr(look, func)(*args, **kwargs)

def call(func, *args, **kwargs):
    ZSP.send((func, args, kwargs))

################################################################################

def change_cursor():
    global fill
    color = tkColorChooser.askcolor(color=fill)[1]
    if color is not None:
        fill = color

def change_canvas():
    color = tkColorChooser.askcolor(color=draw['bg'])[1]
    if color is not None:
        draw.config(bg=color)
        call('config', bg=color)

################################################################################

def motion(event):
    if hold:
        hold.extend([event.x, event.y])
        event.widget.create_line(hold[-4:], fill=fill, tag='TEMP')
        call('create_line', hold[-4:], fill=fill, tag='TEMP')

def press(event):
    global hold
    hold = [event.x, event.y]

def release(event):
    global hold
    if len(hold) > 2:
        event.widget.delete('TEMP')
        event.widget.create_line(hold, fill=fill, smooth=True)
        call('delete', 'TEMP')
        call('create_line', hold, fill=fill, smooth=True)
    hold = []

def delete(event):
    event.widget.delete(ALL)
    call('delete', ALL)

################################################################################

if __name__ == '__main__':
    main()

答案 11 :(得分:0)

示例8

HOST = '127.0.0.1'
PORT = 8080

try:
    from Tkinter import *
except ImportError:
    from tkinter import *

try:
    import tkColorChooser
except ImportError:
    import tkinter.colorchooser as tkColorChooser

try:
    import thread
except ImportError:
    import _thread as thread

import socket
import pickle
import time
import sys

################################################################################

class ZSP:

    'ZSP(socket) -> ZSP'

    def __init__(self, socket):
        'Initialize the Zero SPOTS Protocol object.'
        self.__o_file = socket.makefile('bw', 0)
        self.__i_file = socket.makefile('br', 0)

    def send(self, obj):
        'Send one object.'
        pickle.dump(obj, self.__o_file, pickle.HIGHEST_PROTOCOL)

    def recv(self):
        'Receive one object.'
        return pickle.load(self.__i_file)

################################################################################

class QRP:

    'QRP(ZSP) -> QRP'

    def __init__(self, ZSP):
        'Initialize the Query/Reply Protocol object.'
        self.__ZSP = ZSP
        self.__error = None
        self.__Q_anchor = []
        self.__Q_packet = []
        self.__R_anchor = {}
        self.__Q_lock = thread.allocate_lock()
        self.__R_lock = thread.allocate_lock()
        thread.start_new_thread(self.__thread, ())

    def send_Q(self, ID, obj):
        'Send one query.'
        if self.__error:
            raise self.__error
        self.__ZSP.send((False, ID, obj))

    def recv_Q(self, timeout=None):
        'Receive one query.'
        if self.__error:
            raise self.__error
        if timeout is not None:
            if not isinstance(timeout, (float, int)):
                raise TypeError('timeout must be of type float or int')
            if not timeout >= 0:
                raise ValueError('timeout must be greater than or equal to 0')
        self.__Q_lock.acquire()
        try:
            try:
                if self.__Q_packet:
                    Q = True
                    ID, obj = self.__Q_packet.pop()
                else:
                    Q = False
                    anchor = [thread.allocate_lock()]
                    anchor[0].acquire()
                    self.__Q_anchor.append(anchor)
            finally:
                self.__Q_lock.release()
        except AttributeError:
            raise self.__error
        if Q:
            return ID, obj
        if timeout:
            thread.start_new_thread(self.__Q_thread, (timeout, anchor))
        anchor[0].acquire()
        try:
            Q = anchor[1]
        except IndexError:
            if self.__error:
                raise self.__error
            raise Warning
        return Q

    def send_R(self, ID, obj):
        'Send one reply.'
        if self.__error:
            raise self.__error
        self.__ZSP.send((True, ID, obj))

    def recv_R(self, ID, timeout=None):
        'Receive one reply.'
        if self.__error:
            raise self.__error
        if timeout is not None:
            if not isinstance(timeout, (float, int)):
                raise TypeError('timeout must be of type float or int')
            if not timeout >= 0:
                raise ValueError('timeout must be greater than or equal to 0')
        anchor = [thread.allocate_lock()]
        anchor[0].acquire()
        self.__R_lock.acquire()
        try:
            try:
                self.__R_anchor[ID] = anchor
            finally:
                self.__R_lock.release()
        except AttributeError:
            raise self.__error
        if timeout:
            thread.start_new_thread(self.__R_thread, (timeout, ID))
        anchor[0].acquire()
        try:
            R = anchor[1]
        except IndexError:
            if self.__error:
                raise self.__error
            raise Warning
        return R

    def __thread(self):
        'Private class method.'
        try:
            while True:
                R, ID, obj = self.__ZSP.recv()
                if R:
                    self.__R_lock.acquire()
                    if self.__R_anchor:
                        self.__R_anchor[ID].append(obj)
                        self.__R_anchor[ID][0].release()
                        del self.__R_anchor[ID]
                    self.__R_lock.release()
                else:
                    self.__Q_lock.acquire()
                    if self.__Q_anchor:
                        anchor = self.__Q_anchor.pop()
                        anchor.append((ID, obj))
                        anchor[0].release()
                    else:
                        self.__Q_packet.append((ID, obj))
                    self.__Q_lock.release()
        except Exception:
            error = sys.exc_info()[1]
            if isinstance(error, EOFError):
                self.__error = EOFError
            else:
                self.__error = IOError
            self.__Q_lock.acquire()
            for anchor in self.__Q_anchor:
                anchor[0].release()
            del self.__Q_anchor
            del self.__Q_packet
            self.__Q_lock.release()
            self.__R_lock.acquire()
            for key in self.__R_anchor:
                self.__R_anchor[key][0].release()
            del self.__R_anchor
            self.__R_lock.release()

    def __Q_thread(self, timeout, anchor):
        'Private class method.'
        time.sleep(timeout)
        self.__Q_lock.acquire()
        if not self.__error and anchor in self.__Q_anchor:
            anchor[0].release()
            self.__Q_anchor.remove(anchor)
        self.__Q_lock.release()

    def __R_thread(self, timeout, ID):
        'Private class method.'
        time.sleep(timeout)
        self.__R_lock.acquire()
        if not self.__error and ID in self.__R_anchor:
            self.__R_anchor[ID][0].release()
            del self.__R_anchor[ID]
        self.__R_lock.release()

################################################################################

class QRI:

    'QRI(QRP) -> QRI'

    def __init__(self, QRP):
        'Initialize the Query/Reply Interface object.'
        self.__QRP = QRP
        self.__ID = 0
        self.__lock = thread.allocate_lock()

    def call(self, obj, timeout=None):
        'Send one query and receive one reply.'
        self.__lock.acquire()
        ID = ''.join(chr(self.__ID >> shift & 0xFF) for shift in range(24, -8, -8))
        self.__ID = (self.__ID + 1) % (2 ** 32)
        self.__lock.release()
        self.__QRP.send_Q(ID, obj)
        return self.__QRP.recv_R(ID, timeout)

    def query(self, timeout=None):
        'Receive one query.'
        return self.__QRP.recv_Q(timeout)

    def reply(self, ID, obj):
        'Send one reply.'
        self.__QRP.send_R(ID, obj)

################################################################################

def qri(socket):
    'Construct a QRI object.'
    return QRI(QRP(ZSP(socket)))

################################################################################

def main():
    global hold, fill, draw, look
    hold = []
    fill = '#000000'
    connect()
    root = Tk()
    root.title('Paint 1.0')
    root.resizable(False, False)
    upper = LabelFrame(root, text='Your Canvas')
    lower = LabelFrame(root, text='Their Canvas')
    draw = Canvas(upper, bg='#ffffff', width=400, height=300, highlightthickness=0)
    look = Canvas(lower, bg='#ffffff', width=400, height=300, highlightthickness=0)
    cursor = Button(upper, text='Cursor Color', command=change_cursor)
    canvas = Button(upper, text='Canvas Color', command=change_canvas)
    draw.bind('<Motion>', motion)
    draw.bind('<ButtonPress-1>', press)
    draw.bind('<ButtonRelease-1>', release)
    draw.bind('<Button-3>', delete)
    upper.grid(padx=5, pady=5)
    lower.grid(padx=5, pady=5)
    draw.grid(row=0, column=0, padx=5, pady=5, columnspan=2)
    look.grid(padx=5, pady=5)
    cursor.grid(row=1, column=0, padx=5, pady=5, sticky=EW)
    canvas.grid(row=1, column=1, padx=5, pady=5, sticky=EW)
    root.mainloop()

################################################################################

def connect():
    try:
        start_client()
    except:
        start_server()
    thread.start_new_thread(processor, ())

def start_client():
    global QRI
    server = socket.socket()
    server.connect((HOST, PORT))
    QRI = qri(server)

def start_server():
    global QRI
    server = socket.socket()
    server.bind(('', PORT))
    server.listen(1)
    QRI = qri(server.accept()[0])

def processor():
    while True:
        ID, (func, args, kwargs) = QRI.query()
        getattr(look, func)(*args, **kwargs)

def call(func, *args, **kwargs):
    try:
        QRI.call((func, args, kwargs), 0.05)
    except:
        pass

################################################################################

def change_cursor():
    global fill
    color = tkColorChooser.askcolor(color=fill)[1]
    if color is not None:
        fill = color

def change_canvas():
    color = tkColorChooser.askcolor(color=draw['bg'])[1]
    if color is not None:
        draw['bg'] = color
        draw.config(bg=color)
        call('config', bg=color)

################################################################################

def motion(event):
    if hold:
        hold.extend([event.x, event.y])
        event.widget.create_line(hold[-4:], fill=fill, tag='TEMP')
        call('create_line', hold[-4:], fill=fill, tag='TEMP')

def press(event):
    global hold
    hold = [event.x, event.y]

def release(event):
    global hold
    if len(hold) > 2:
        event.widget.delete('TEMP')
        event.widget.create_line(hold, fill=fill, smooth=True)
        call('delete', 'TEMP')
        call('create_line', hold, fill=fill, smooth=True)
    hold = []

def delete(event):
    event.widget.delete(ALL)
    call('delete', ALL)

################################################################################

if __name__ == '__main__':
    main()