python核心编程--第十八章

对于第十六章和第十七章,由于是网络编程,自己写出的程序居然不能运行,很让人郁闷,所以直接跳过第十六章的课后习题和十七章的内容,直接进入十八章。

反正用了线程来写全双工的通信,结果就是:程序崩溃了。。。。。。

直接郁闷。

那么,开始学习第十八章吧。

18.1 引言/动机

多线程编程对于某些任务来说,是最理想的。这些任务具有以下特点:它们本质就是异步的,需要有多个并发事务,各个事务的运行顺序可以是不确定的,随机的,不可预测的。这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标。根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。

一个顺序执行的程序要从每个I/O(输入/输出)终端信道检查用户的输入时,程序无论如何也不能在读取I/O终端信道的时候阻塞。因为用户输入的到达时不确定的,则阻塞会导致其它I/O信息的数据不能被处理。顺序执行的程序必须使用非阻塞I/O,或是带有计时器的阻塞I/O(这样才能保证阻塞只是暂时的)。

由于顺序执行的程序只有一个线程执行。它要保证它要做的多任务,不会有某个任务占用太多的时间,而且要合理的分配用户的响应时间。执行多任务的顺序执行的程序一般程序控制流程都很复杂,难以理解。

使用多线程编程和一个共享的数据结构如Queue,这种程序任务可以用几个功能单一的线程来组织:

UserRequesThread:负责读取客户的输入,可能是一个I/O信道。程序可能创建多个线程,每个客户一个,请求会被放入队列中。

RequestProcessor:一个负责从队列中获取并处理请求的线程,它为下面那种线程提供输出

ReplyThread:负责把给用户的输出取出来,如果是网络应用程序就把结果发送出去,否则就保存到本地文件系统或数据库中。

18.2 线程和进程

18.2.1 什么是进程?

18.2.1 什么是进程?

计算机程序只不过是磁盘中可执行的,二进制的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命周期。进程(重量级进程)是程序的一次执行,每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间。进程也可以通过fork和spawn操作来完成其它的任务。不过各个进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(IPC),而不能直接共享信息。

18.2.2 什么是线程

线程(轻量级进程)跟进程有些相似,不同的是:所有的线程运行在同一个进程中,共享相同的运行环境。它们可以想象成是在主进程或“主线程”中并行运行的“迷你进程”。

线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便的共享数据以及相互通讯。线程一般都是并发执行的,正式由于这种并行和数据共享的机制使得多个任务的合作变成可能。实际上,在单CPU的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一会,然后就把CPU让出来,让其它的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其它的线程共享运行的结果。

当然,这样的共享并不是完全没有危险的。如果多个线程共同访问同一片数据,则由于数据访问的顺序不一样,有可能导致数据结果的不一致问题。

另一个需要注意的地方是:由于有的函数会在完成之前阻塞住,在没有特别为多线程做修改的情况下,这种“贪婪”的函数会让CPU的市价分配有所倾斜。导致各个线程分配到的运行时间可能不尽相同,不尽公平。

18.3 python,线程和全局解释器锁

对python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,python虚拟机按以下方式执行:

1. 设置GIL

2. 切换到一个线程去运行

3. 运行:    

    a. 指定数量的字节码指令,或者

    b. 线程主动让出控制(可以调用time.sleep(0))

4. 把线程设置为睡眠状态

5. 解锁GIL

6. 再次重复以上所有步骤

18.3.2 退出线程

当一个线程结束计算,它就退出了。线程可以调用thread.exit()之类的退出函数,也可以使用python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。不过,你不可以直接“杀掉”(“kill”)一个线程。

主线程应该是一个好的管理者,它要了解每个线程都要做些什么事,线程都需要什么数据和什么参数,以及在线程结束的时候,它们都提供了什么结果。这样,主线程就可以把各个线程的结果组合成一个有意义的最后结果。

18.3.4 没有线程支持的情况

from time import sleep, ctime
def loop0():
    print "start loop 0 at:", ctime()
    sleep(4)
    print "loop 0 done at:", ctime()
def loop1():
    print "start loop 1 at:", ctime()
    sleep(2)
    print "loop 1 done at:", ctime()
def main():
    print "starting at:", ctime()
    loop0()
    loop1()
    print "all DONE at:", ctime()

if __name__ == "__main__":
    main()
程序输出:
>>> 
starting at: Sat Jun 22 22:54:25 2013
start loop 0 at: Sat Jun 22 22:54:25 2013
loop 0 done at: Sat Jun 22 22:54:29 2013
start loop 1 at: Sat Jun 22 22:54:29 2013
loop 1 done at: Sat Jun 22 22:54:31 2013
all DONE at: Sat Jun 22 22:54:31 2013
18.3.5 python的threading模块

Python 提供了几个用于多线程编程的模块,包括thread, threading 和Queue 等。thread 和threading 模块允许程序员创建和管理线程。thread 模块提供了基本的线程和锁的支持,而threading提供了更高级别,功能更强的线程管理的功能。Queue 模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

18.4 thread模块

thread 模块函数
start_new_thread(function,
args, kwargs=None)                 产生一个新的线程,在新线程中用指定的参数和可选的
                                                kwargs来调用这个函数。
allocate_lock()                         分配一个LockType 类型的锁对象
exit()                                         让线程退出


LockType类型锁对象方法
acquire(wait=None)                     尝试获取锁对象
locked()                                     如果获取了锁对象返回True,否则返回False
release()                                     释放锁

我们来看一个简单的程序:

from time import sleep, ctime
import thread
def loop0():
    print "start loop 0 at:", ctime()
    sleep(4)
    print "loop 0 done at:", ctime()
def loop1():
    print "start loop 1 at:", ctime()
    sleep(2)
    print "loop 1 done at:", ctime()
def main():
    print "starting at:", ctime()
    thread.start_new_thread(loop0, ())
    thread.start_new_thread(loop1, ())
    sleep(6)
    print "all DONE at:", ctime()

if __name__ == "__main__":
    main()
程序输出有点乱:
>>> 
starting at: Sat Jun 22 23:02:59 2013
start loop 0 at:start loop 1 at:  Sat Jun 22 23:02:59 2013Sat Jun 22 23:02:59 2013

loop 1 done at: Sat Jun 22 23:03:01 2013
loop 0 done at: Sat Jun 22 23:03:03 2013
all DONE at: Sat Jun 22 23:03:05 2013
代码中sleep(6)是为了给两个线程提供运行时间,那有没有什么方法来代替sleep(6)呢?

使用线程和锁

import thread
from time import sleep, ctime

loops = [4,2]

def loop(nloop, nsec, lock):
    print "start loop", nloop, "at:", ctime()
    sleep(nsec)
    print "loop",nloop, "done at:", ctime()
    lock.release()
def main():
    print "starting at:", ctime()
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate_lock()
        lock.acquire()
        locks.append(lock)

    for i in nloops:
        thread.start_new_thread(loop, (i, loops[i], locks[i]))

    for i in nloops:
        while locks[i].locked():
            pass
    print "all DONE at:", ctime()

if __name__ == "__main__":
    main()
但是貌似程序有点错误,运行时候死锁了。。。。。。

不过突然又行了,结果运行结果还是有点搞笑:

>>> 
starting at: Sat Jun 22 23:19:41 2013
start loopstart loop  01  at:at:  Sat Jun 22 23:19:41 2013Sat Jun 22 23:19:41 2013

loop 1 done at: Sat Jun 22 23:19:43 2013
loop 0 done at: Sat Jun 22 23:19:45 2013
all DONE at: Sat Jun 22 23:19:45 2013

18.5 threading模块

threading 模块对象                                         描述
Thread                                             表示一个线程的执行的对象
Lock                                                 锁原语对象(跟thread 模块里的锁对象相同)
RLock                                         可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定)。
Condition                                     条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。
                                                    如,状态的改变或值的改变。
Event                                     通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,
                                            所有的线程都会被激活。
Semaphore                                 为等待锁的线程提供一个类似“等候室”的结构
BoundedSemaphore                             与Semaphore 类似,只是它不允许超过初始值
Timer                                             与Thread 相似,只是,它要等待一段时间后才开始运行。

核心提示:守护线程
另一个避免使用thread 模块的原因是,它不支持守护线程。当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。有时,我们并不期望这种行为,这时,就引入了守护线程的概念
threading 模块支持守护线程,它们是这样工作的:守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求,它就在那等着。如果你设定一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。就像你在第16 章网络编程看到的,服务器线程运行在一个无限循环中,一般不会退出。
如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的daemon 属性。即,在线程开始(调用thread.start())之前,调用setDaemon()函数设定线程的daemon 标志(thread.setDaemon(True))就表示这个线程“不重要”
如果你想要等待子线程完成再退出, 那就什么都不用做, 或者显式地调用thread.setDaemon(False)以保证其daemon 标志为False。你可以调用thread.isDaemon()函数来判断其daemon 标志的值。新的子线程会继承其父线程的daemon 标志。整个Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。

18.5.1 Thread类

三种方式调用Thread类:

1. 创建一个Thread的实例,传给它一个函数

2. 创建一个Thread的实例,传给它一个可调用的类对象

3. 从Thread派生出一个子类,创建一个这个子类的实例

函数                                                 描述
start()                                             开始线程的执行
run()                                             定义线程的功能的函数(一般会被子类重写)
join(timeout=None)                         程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout 秒
getName()                                     返回线程的名字
setName(name)                               设置线程的名字
isAlive()                                         布尔标志,表示这个线程是否还在运行中
isDaemon()                                     返回线程的daemon 标志
setDaemon(daemonic)                把线程的daemon 标志设为daemonic(一定要在调用start()函数前调用)

创建一个Thread的实例,传给它一个函数:

import threading
from time import sleep, ctime

loops = [4,2]

def loop(nloop, nsec):
    print "start loop", nloop, "at:", ctime()
    sleep(nsec)
    print "loop", nloop, "done at:", ctime()

def main():
    print "starting at:", ctime()
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = threading.Thread(target=loop, args = (i, loops[i]))
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print "all DONE at:", ctime()

if __name__ == "__main__":
    main()
程序输出:
>>> 
starting at: Sat Jun 22 23:34:54 2013
start loopstart loop  01  at:at:  Sat Jun 22 23:34:54 2013Sat Jun 22 23:34:54 2013

loop 1 done at: Sat Jun 22 23:34:56 2013
loop 0 done at: Sat Jun 22 23:34:58 2013
all DONE at: Sat Jun 22 23:34:58 2013


创建一个Thread的实例,传给它一个可调用的类对象
import threading
from time import sleep, ctime

loops = [4,2]

class ThreadFunc(object):
    def __init__(self, func, args, name=""):
        self.name = name
        self.func = func
        self.args = args
    def __call__(self):
        self.func(*self.args)
def loop(nloop, nsec):
    print "start loop", nloop, "at:", ctime()
    sleep(nsec)
    print "loop", nloop, "done at:", ctime()
def main():
    print "start at:", ctime()
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = threading.Thread(target = ThreadFunc(loop, (i, loops[i]),
                                                 loop.__name__))
        threads.append(t)

    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print "all DONE at:", ctime()

if __name__ == "__main__":
    main()
程序输出:
>>> 
start at: Sat Jun 22 23:46:14 2013
start loopstart loop  01  at:at:  Sat Jun 22 23:46:14 2013Sat Jun 22 23:46:14 2013

loop 1 done at: Sat Jun 22 23:46:16 2013
loop 0 done at: Sat Jun 22 23:46:18 2013
all DONE at: Sat Jun 22 23:46:18 2013


从Thread派生出一个子类,创建一个这个子类的实例

import threading
from time import sleep, ctime

loops = (4,2)

class MyThread(threading.Thread):
    def __init__(self, func, args, name=""):
        super(MyThread, self).__init__()
        self.name = name
        self.func = func
        self.args = args
    def run(self):
        self.func(*self.args)
def loop(nloop, nsec):
    print "start loop",nloop,"at:", ctime()
    sleep(nsec)
    print "loop", nloop, "done at:", ctime()
def main():
    print "start at:", ctime()
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = MyThread(loop, (i, loops[i]),loop.__name__)
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print "all DONE at:", ctime()

if __name__ == "__main__":
    main()
程序输出:

>>> 
start at: Sun Jun 23 12:40:25 2013
start loopstart loop  01  at:at:  Sun Jun 23 12:40:25 2013Sun Jun 23 12:40:25 2013

loop 1 done at: Sun Jun 23 12:40:27 2013
loop 0 done at: Sun Jun 23 12:40:29 2013
all DONE at: Sun Jun 23 12:40:29 2013

18.5.4 斐波那契,阶乘和累加和

import threading
from time import ctime,sleep

class MyThread(threading.Thread):
    def __init__(self, func, args, name=""):
        super(MyThread, self).__init__()
        self.name = name
        self.func = func
        self.args = args
    def getResult(self):
        return self.res
    def run(self):
        print "starting", self.name,"at:",ctime()
        self.res = self.func(*self.args)
        print self.name,"finished at:", ctime()

def fib(x):
    sleep(0.005)
    if x < 2:
        return 1
    return (fib(x - 2) + fib(x - 1))

def fac(x):
    sleep(0.1)
    if x < 2:
        return 1
    return (x * fac(x - 1))

def sum(x):
    sleep(0.1)
    if x < 2:
        return 1
    return (x + sum(x - 1))

funcs = [fib, fac, sum]
n = 12

def main():
    nfuncs = range(len(funcs))
    print "***single thread"
    for i in nfuncs:
        print "starting",funcs[i].__name__,"at:", ctime()
        print funcs[i](n)
        print funcs[i].__name__,"finished at:", ctime()
    print "\n***multiple threads"
    threads = []
    for i in nfuncs:
        t = MyThread(funcs[i],(n,), funcs[i].__name__)
        threads.append(t)
    for i in nfuncs:
        threads[i].start()
    for i in nfuncs:
        threads[i].join()
        print threads[i].getResult()
    print "all Done"

if __name__ == "__main__":
    main()
程序输出:

>>> 
***single thread
starting fib at: Sun Jun 23 12:53:38 2013
233
fib finished at: Sun Jun 23 12:53:40 2013
starting fac at: Sun Jun 23 12:53:40 2013
479001600
fac finished at: Sun Jun 23 12:53:42 2013
starting sum at: Sun Jun 23 12:53:42 2013
78
sum finished at: Sun Jun 23 12:53:43 2013

***multiple threads
startingstartingstarting   fibfacsum   at:at:at:   Sun Jun 23 12:53:43 2013Sun Jun 23 12:53:43 2013Sun Jun 23 12:53:43 2013


facsum  finished at:finished at:  Sun Jun 23 12:53:44 2013Sun Jun 23 12:53:44 2013

fib finished at: Sun Jun 23 12:53:45 2013
233
479001600
78
all Done

18.5.5 生产者--消费者问题和Queue模块

常用的queue模块的属性

queue(size)                创建一个大小为size的queue对象

qsize()            返回队列的大小

empty()            如果队列为空返回True,否则返回false

full()                如果队列已满返回True,否则返回false

put(item, block = 0)    把item放到队列中,如果给了block(不为0),函数会一直阻塞到队列中有空间为止

get(block = 0)        从队列中取一个对象,如果给了block(不为0),函数会一直阻塞到队列中有对象为止。

queue模块可以用来进行线程间通讯,让各个线程之间共享数据。

from random import randint
from time import sleep
from Queue import Queue
import threading
from time import ctime

class MyThread(threading.Thread):
    def __init__(self, func, args, name=""):
        super(MyThread, self).__init__()
        self.name = name
        self.func = func
        self.args = args
    def getResult(self):
        return self.res
    def run(self):
        print "starting", self.name,"at:",ctime()
        self.res = self.func(*self.args)
        print self.name,"finished at:", ctime()
def writeQ(queue):
    print "producing object for Q..."
    queue.put("xxx",1)
    print "size now", queue.qsize()
def readQ(queue):
    val = queue.get(1)
    print "consumed object from Q...size now", queue.qsize()
def writer(queue, loops):
    for i in range(loops):
        writeQ(queue)
        sleep(randint(1,3))
def reader(queue, loops):
    for i in range(loops):
        readQ(queue)
        sleep(randint(2,5))
funcs = [writer, reader]
nfuncs = range(len(funcs))

def main():
    nloops = randint(2,5)
    q = Queue(32)

    threads = []
    for i in nfuncs:
        t = MyThread(funcs[i], (q, nloops),funcs[i].__name__)
        threads.append(t)
    for i in nfuncs:
        threads[i].start()
    for i in nfuncs:
        threads[i].join()
    print "all DONE"

if __name__ == "__main__":
    main()
程序输出:

>>> 
startingstarting  writerreader  at:at:  Sun Jun 23 13:15:29 2013Sun Jun 23 13:15:29 2013

producing object for Q...
size nowconsumed object from Q...size now  00

producing object for Q...
size nowconsumed object from Q...size now  00

producing object for Q...
size nowconsumed object from Q...size now  00

writer finished at: Sun Jun 23 13:15:36 2013
reader finished at: Sun Jun 23 13:15:38 2013
all DONE

忽然之间,就到了练习了。

18.7 练习

18-1. 进程与线程。线程与进程的区别是什么?

还是从定义入手比较好:

进程(重量级进程)是程序的一次执行,每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间。进程也可以通过fork和spawn操作来完成其它的任务。不过各个进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(IPC),而不能直接共享信息。

线程(轻量级进程)跟进程有些相似,不同的是:所有的线程运行在同一个进程中,共享相同的运行环境。它们可以想象成是在主进程或“主线程”中并行运行的“迷你进程”。

18-2. Python 的线程。在Python 中,哪一种多线程的程序表现得更好,I/O 密集型的还是计算密集型的?

对于这道题,虽然书本上给出了答案,但是我无法做出判断,因为对I/O 密集型和计算密集型并不了解。

18-3. 线程。你认为,多CPU 的系统与一般的系统有什么大的不同?多线程的程序在这种系统上的表现会怎么样?

多CPU可以实现真正的并发处理。

18-4. 线程和文件。把练习9-19 的答案做一些改进。我们要得到一个字节值,一个文件名然后显示在文件中那个字节出现了多少次。假设这个文件非常的大。文件是可以有多个读者的,那我们就可以创建多个线程,每个线程负责文件的一部分。最后,把所有的线程的结果相加。使用timeit()对单线程和多线程分别进行计时,对性能的改进进行讨论。

这里我有个疑问是:每个线程负责文件的一部分,这是如何办到的???难道我要把一个文件,比如有100行,我分成10个部分,每个部分10行,对10个部分用十个线程来进行读取,然后相加??

我这里做了更改,同一个文件读取十次,一个用到单线程,一个用到多线程,效率上多线程确实比单线程快,但是我不知道在多线程中如何保留读取的次数。

代码如下:

import threading
import time

print "single threading:"
count = 0
timeBegin = time.clock()
for i in range(10):
    with open("data.txt") as fobj:
        for item in fobj:
            count += item.count("os")
timeEnd = time.clock()
print "os occures:%d" % count
print "using" , (timeEnd - timeBegin),"s"

print "multiple threading:"
count = 0
def read(count):
    with open("data.txt") as fobj:
        for item in fobj:
            count += item.count("os")
threads = []
timeBegin = time.clock()
for i in range(10):
    t = threading.Thread(target = read, args = (count,))
    threads.append(t)
for i in range(10):
    threads[i].start()
for i in range(10):
    threads[i].join()
timeEnd = time.clock()
print "os occures:%d" % count
print "using",(timeEnd - timeBegin),"s"
程序输出:

>>> 
single threading:
os occures:24640
using 0.101824816326 s
multiple threading:
os occures:0
using 0.0638655391622 s
我们会发现,多线程快乐近一倍。如果读取100个文件呢?

>>> 
single threading:
os occures:246400
using 0.763980252032 s
multiple threading:
os occures:0
using 0.628991800292 s
多线程根本就快不了多少这样。。。。。

对网络编程头疼,后面的习题就不做。

最近在思考自己的职业发展方向,想选择一门技术来专研,可惜一直未找到自己喜欢的技术,真心郁闷。




转载于:https://my.oschina.net/voler/blog/139475

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值