python学习日记-2016.7.25

1.多进程

multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:

from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name): 
  print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__': 
  print('Parent process %s.' % os.getpid()) 
  p = Process(target=run_proc, args=('test',)) 
  print('Child process will start.') 
  p.start() 
  p.join() 
  print('Child process end.')

运行结果如下

Parent process 10960
child process will start
run process test (7448)...
child process end

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。

join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

2.进程池

from multiprocessing 
import Poolimport os, time, random
def a(name):    
  print('run task %s (%s)' %(name,os.getpid()))    
  start=time.time()    
  time.sleep(random.random()*3)    
  end=time.time()    
  print('task %s runs %0.2f seconds' %(name,(end-start)))

if __name__=='__main__':    
  print('parent process %s' %os.getpid())    
  p=Pool(4)    
  for i in range(5):        
    p.apply_async(a, args=(i,))    
    print('wait for all subprocessed done')    
    p.close()    
    p.join()    
    print('all subprocess done')

执行结果如下:

parent process 9404
wait for all subprocessed done
run task 0 (4896)
run task 1 (1964)
run task 2 (2296)
run task 3 (11092)
task 1 runs 0.59 seconds
run task 4 (1964)
task 4 runs 1.08 seconds
task 2 runs 2.25 seconds
task 3 runs 2.78 seconds
task 0 runs 2.97 seconds
all subprocess done

代码解读:
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。请注意输出的结果,task0,1,2,3是立刻执行的,而task4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:p = Pool(5)就可以同时跑5个进程。由于Pool的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。

3.子进程

很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。下面的例子演示了如何在Python代码中运行命令nslookup www.python.org,这和命令行直接运行的效果是一样的:

import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])print('Exit code:', r)

运行结果:

$ nslookup www.python.org
Server: 192.168.19.4
Address: 192.168.19.4#53

Non-authoritative answer:
www.python.org canonical name = python.map.fastly.net.
Name: python.map.fastly.net
Address: 199.27.79.223
Exit code: 0

如果子进程还需要输入,则可以通过communicate()方法输入:

import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)

上面的代码相当于在命令行执行命令nslookup,然后手动输入:
set q=mx
python.org
exit

运行结果如下:

$ nslookup
Traceback (most recent call last):
  File "E:/intelliJProject/firstpython/b.py", line 7, in <module>
    print(output.decode('utf-8'))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8 in position 2: invalid continuation byte

结果发现出错了,不能使用utf-8,那么改成gbk之后再运行就可以了

$ nslookup
默认服务器:  UnKnown
Address:  172.20.1.4

> > 服务器:  UnKnown
Address:  172.20.1.4

python.org  MX preference = 50, mail exchanger = mail.python.org

python.org  nameserver = ns3.p11.dynect.net
python.org  nameserver = ns4.p11.dynect.net
python.org  nameserver = ns1.p11.dynect.net
python.org  nameserver = ns2.p11.dynect.net
mail.python.org internet address = 188.166.95.178
mail.python.org AAAA IPv6 address = 2a03:b0c0:2:d0::71:1
ns1.p11.dynect.net  internet address = 208.78.70.11
ns2.p11.dynect.net  internet address = 204.13.250.11
ns3.p11.dynect.net  internet address = 208.78.71.11
ns4.p11.dynect.net  internet address = 204.13.251.11
> 
Exit code: 0

4.进程间的通信

Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。

我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

#-*- coding=utf-8 -*-
from multiprocessing import Process,Queue
import os,time,random
def write(q):    
  print('process to write:%s' %os.getpid())    
  for value in ['A','B','C']:        
    print('put %s to queue...'%value)        
    q.put(value)        
    time.sleep(random.random())

def read(q):    
  print('process to read:%s' %os.getpid())    
  while(True):        
    value=q.get(True)        
    print('get %s frome queue...' %value)

if __name__=='__main__':    
  q=Queue()    
  pw=Process(target=write, args=(q,))    
  pr=Process(target=read, args=(q,))    
  pw.start()    
  pr.start()    
  pw.join()    
  pr.terminate()

运行结果:

process to write:9980
put A to queue...
process to read:6424
get A frome queue...
put B to queue...
get B frome queue...
put C to queue...
get C frome queue...

5.多线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成。我们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。Python的标准库提供了两个模块:_thread
和threading,_thread是低级模块,threading是高级模块,对_thread
进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()
开始执行:

import time, threading
# 新线程执行的代码:
def loop(): 
  print('thread %s is running...' % threading.current_thread().name) 
  n = 0 
  while n < 5: 
    n = n + 1 
    print('thread %s >>> %s' % (threading.current_thread().name, n))
    time.sleep(1) 
print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

执行结果如下:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5t
hread LoopThread ended.
thread MainThread ended.

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()
函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread
,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

6.线程锁

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
来看看多个线程同时操作一个变量怎么把内容给改乱了:

#-*- coding=utf-8 -*-
import time,threading
balance=0
def change_it(n):    
  global balance    
  balance+=n    
  balance-=n

def runthread(n):    
  for i in range(100000):
    change_it(n)

t1=threading.Thread(target=runthread, args=(5,))
t2=threading.Thread(target=runthread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

每次运行的结果都不一样

13
5
5
0
...

我们定义了一个共享变量balance,初始值为0,并且启动两个线程,先存后取,理论上结果应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。
原因是因为高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:balance = balance + n也分两步:
计算balance + n,存入临时变量中;
将临时变量的值赋给balance。
也就是可以看成:
x = balance + n
balance = x

由于x是局部变量,两个线程各自都有自己的x,当代码正常执行时:

初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2 # balance = 0
结果 balance = 0

但是t1和t2是交替运行的,如果操作系统以下面的顺序执行t1、t2:

初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
结果 balance = -8

究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance的时候,别的线程一定不能改。如果我们要确保balance计算正确,就要给change_it()
上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:

balance = 0
lock = threading.Lock()
def run_thread(n): 
  for i in range(100000): 
    # 先要获取锁: 
    lock.acquire() 
    try: 
    # 放心地改吧: 
      change_it(n) 
    finally: 
      # 改完了一定要释放锁: 
      lock.release()

当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

7.ThreadLocal

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

8.正则表达式

参考
正则表达式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958...
    喵在野阅读 3,572评论 0 1
  • 1.线程的基本概念 1.1 线程 线程是应用程序最小的执行单元,线程与进程类似,进程可以看做程序的一次执行,而线程...
    XYZeroing阅读 934评论 1 16
  • 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,...
    chen_000阅读 459评论 0 0
  • 总结 代码 总结 1. 断点续传 思路也主要是逻辑上的,不是真正网络的断点续传,而是在Table-1:link_l...
    OldSix1987阅读 399评论 0 0
  • 人都是善良的吗?我觉得不是的,而且善良并不是只拥有一颗柔软的心,还要有温暖的举动。所以对于那些心善良,又能做出善良...
    我不够好所以你别来阅读 479评论 0 0