多进程详解
在Python中如何建立多线程?
经过Thread建立多线程css
经过Thread子类建立多线程python
python的threading模块是对thread作了一些包装的,能够更加方便的被使用,线程的方法和进程的基本类似,这里就很少赘述,下面举几个栗子:数据库
#例一线程的基本用法
#coding=utf-8
import threading
import time
def xianyu():
print("咸鱼普拉思")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=xianyu)
t.start() #启动线程,即让线程开始执行
输出:
咸鱼普拉思
咸鱼普拉思
咸鱼普拉思
咸鱼普拉思
咸鱼普拉思
[Finished in 1.1s]
#例二使用Threading子类建立多线程
#coding=utf-8
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i)
print(msg)
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
输出:
I'm Thread-1 @ 0
I'
m Thread-2 @ 0
I'm Thread-3 @ 0
I'
m Thread-4 @ 0
I'm Thread-5 @ 0
I'
m Thread-1 @ 1
I'm Thread-2 @ 1
I'
m Thread-4 @ 1
I'm Thread-3 @ 1
I'
m Thread-5 @ 1
I'm Thread-1 @ 2
I'
m Thread-2 @ 2
I'm Thread-5 @ 2
I'
m Thread-4 @ 2
I'm Thread-3 @ 2
[Finished in 3.2s]
多线程和多进程的执行有什么区别?
多进程是多份程序同时执行编程
多线程是在一份程序下多个执行指针同时执行设计模式
多线程并不须要线程间通讯,线程间共享全局变量,进程间不共享全局变量安全
进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),可是它可与同属一个进程的其余的线程共享进程所拥有的所有资源。微信
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。 多线程
进程在执行过程当中拥有独立的内存单元,而多个线程共享内存,从而极大地提升了程序的运行效率 并发
且线程不可以独立执行,必须依存在进程中 app
线程执行开销小,但不利于资源的管理和保护,而进程正相反
线程的几种状态
线程在执行过程当中,若是中途执行sleep语句时,线程会进入到阻塞状态,当sleep结束以后,线程进入就绪状态,等待调度而线程调度将自行选择一个线程执行 。
具体线程状态变换参照下图:

线程之间共享全局变量
举个栗子:
from threading import Thread
import time
num = 100
def work1():
global num
for i in range(3):
num += 1
print("----in work1, num is %d---"%num)
def work2():
global num
print("----in work2, num is %d---"%num)
print("---线程建立以前g_num is %d---"%num)
t1 = Thread(target=work1)
t1.start()
#延时一会,保证t1线程中的事情作完
time.sleep(1)
t2 = Thread(target=work2)
t2.start()
输出:
---线程建立以前g_num is 100---
----in work1, num is 103---
----in work2, num is 103---
[Finished in 1.1s]
总结:
在一个进程内的全部线程共享全局变量,可以在不适用其余方式的前提下完成多线程之间的数据共享(这点要比多进程要好)
缺点就是,线程是对全局变量随意遂改可能形成多线程之间对全局变量的混乱(即线程非安全)
什么是线程不安全?
举个栗子:
from threading import Thread
import time
num = 0
def test1():
global num
for i in range(1000000):
num += 1
print("---test1---num=%d"%g_numnum
def test2():
global num
for i in range(1000000):
num += 1
print("---test2---num=%d"%num)
p1 = Thread(target=test1)
p1.start()
# time.sleep(3)
p2 = Thread(target=test2)
p2.start()
print("---num=%d---"%num)
输出:
当time.sleep(3),没有取消屏蔽时
---num=235159---
---test1---num=1172632
---test2---num=1334237
[Finished in 0.3s]
当time.sleep(3),取消屏蔽时
---test1---num=1000000
---num=1014670---
---test2---num=2000000
[Finished in 3.3s]
上面举的栗子就是线程不安全的现象,具体能够解释为,线程1对数据num进行自增的时候,获取的值是num=0,此时系统把线程1调度为”sleeping”状态 ,而线程2在作一样操做时获取的num值仍是为0,同时作自增1的操做,这时在线程2中num的值为1,此时系统把线程2调度为”sleeping”状态,线程1再作自增操做时,num仍是刚刚获取到的0,长此往复下去,最终的结果就不是咱们所预期的了。
没有控制多个线程对同一资源的访问,对数据形成破坏,使得线程运行的结果不可预期 ,这种现象就是线程不安全。
如何避免线程不安全的现象发生?
当多个线程几乎同时修改某一个共享数据的时候,须要进行同步控制,线程同步可以保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其余线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其余的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操做,从而保证了多线程状况下数据的正确性。
举个栗子:
from threading import Thread, Lock
import time
num = 0
def test1():
global num
for i in range(1000000):
#True表示堵塞 即若是这个锁在上锁以前已经被上锁了,那么这个线程会在这里一直等待到解锁为止
#False表示非堵塞,即无论本次调用可以成功上锁,都不会卡在这,而是继续执行下面的代码
mutexFlag = mutex.acquire(True)
if mutexFlag:
num += 1
mutex.release()
print("---test1---num=%d"%num)
def test2():
global num
for i in range(1000000):
mutexFlag = mutex.acquire(True) #True表示堵塞
if mutexFlag:
num += 1
mutex.release()
print("---test2---num=%d"%num)
#建立一个互斥锁
#这个所默认是未上锁的状态
mutex = Lock()
p1 = Thread(target=test1)
p1.start()
p2 = Thread(target=test2)
p2.start()
print("---num=%d---"%num)
输出:
---num=61866---
---test1---num=1861180
---test2---num=2000000
当一个线程调用锁的acquire()方法得到锁时,锁就进入“locked”状态。每次只有一个线程能够得到锁。若是此时另外一个线程试图得到这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁以后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来得到锁,并使得该线程进入运行状态。
加锁确保了某段关键代码只能由一个线程从头至尾完整地执行,可是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地降低了,因为能够存在多个锁,不一样的线程持有不一样的锁,并试图获取对方持有的锁时,可能会形成死锁
什么是死锁?
在线程间共享多个资源的时候,若是两个线程分别占有一部分资源而且同时等待对方的资源,就会形成死锁。
举个栗子:
#coding=utf-8
import threading
import time
class MyThread1(threading.Thread):
def run(self):
if mutexA.acquire():
print(self.name+'----do1---up----')
time.sleep(1)
if mutexB.acquire():
print(self.name+'----do1---down----')
mutexB.release()
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
if mutexB.acquire():
print(self.name+'----do2---up----')
time.sleep(1)
if mutexA.acquire():
print(self.name+'----do2---down----')
mutexA.release()
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
生产者与消费者模型
咱们能够经过生产者和消费者模型来解决线程的同步,和线程安全。
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语(能够理解为原子操做,即要么不作,要么就作完),可以在多线程中直接使用,可使用队列来实现线程间的同步。
举个栗子:
#encoding=utf-8
import threading
import time
#python2中
# from Queue import Queue
#python3中
from queue import Queue
class Producer(threading.Thread):
def run(self):
global queue
count = 0
while True:
if queue.qsize() < 1000:
for i in range(100):
count = count +1
msg = '生成产品'+str(count)
queue.put(msg)
print(msg)
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global queue
while True:
if queue.qsize() > 100:
for i in range(3):
msg = self.name + '消费了 '+queue.get()
print(msg)
time.sleep(1)
if __name__ == '__main__':
queue = Queue()
for i in range(500):
queue.put('初始产品'+str(i))
for i in range(2):
p = Producer()
p.start(),
for i in range(5):
c = Consumer()
c.start()
什么是生产者与消费者模型?
生产者消费者模式是经过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通信,而经过阻塞队列来进行通信,因此生产者生产完数据以后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就至关于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦。
想使用全局变量有不想加锁怎么办?
在多线程环境下,每一个线程都有本身的数据。一个线程使用本身的局部变量比使用全局变量好,由于局部变量只有线程本身能看见,不会影响其余线程,而全局变量的修改必须加锁。
举个栗子:
import threading
# 建立全局ThreadLocal对象:
local_school = threading.local()
def process_student():
# 获取当前线程关联的student:
std = local_school.student
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()
t1 = threading.Thread(target= process_thread, args=('咸鱼',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('普拉思',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
输出:
Hello, 咸鱼 (in Thread-A)
Hello, 普拉思 (in Thread-B)
全局变量local_school就是一个ThreadLocal对象,每一个Thread对它均可以读写student属性,但互不影响。你能够把local_school当作全局变量,但每一个属性如local_school.student都是线程的局部变量,能够任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
能够理解为全局变量local_school是一个dict,不但能够用local_school.student,还能够绑定其余变量,如local_school.teacher等等。
ThreadLocal最经常使用的地方就是为每一个线程绑定一个数据库链接,HTTP请求,用户身份信息等,这样一个线程的全部调用到的处理函数均可以很是方便地访问这些资源。
一个ThreadLocal变量虽然是全局变量,但每一个线程都只能读写本身线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题
同步调用和异步调用?
同步调用就是你喊你朋友吃饭,你朋友在忙,你就一直在那等,等你朋友忙完了 ,大家一块儿去。
异步调用就是你喊你朋友吃饭,你朋友说知道了,待会忙完去找你 ,你就去作别的了。
举个栗子:
from multiprocessing import Pool
import time
import os
def test():
print("---进程池中的进程---pid=%d,ppid=%d--"%(os.getpid(),os.getppid()))
for i in range(3):
print("----%d---"%i)
time.sleep(1)
return "hahah"
def test2(args):
print("---callback func--pid=%d"%os.getpid())
print("---callback func--args=%s"%args)
pool = Pool(3)
pool.apply_async(func=test,callback=test2)
time.sleep(5)
print("----主进程-pid=%d----"%os.getpid())
输出:
---进程池中的进程---pid=9401,ppid=9400--
----0---
----1---
----2---
---callback func--pid=9400
---callback func--args=hahah
----主进程-pid=9400----
注意:这里的callback是由主进程执行的,当子进程死亡,主进程回调函数。
什么是GIL锁?
Python全局解释锁(GIL)简单来讲就是一个互斥体(或者说锁),这样的机制只容许一个线程来控制Python解释器。这就意味着在任何一个时间点只有一个线程处于执行状态。
因此在python中多线程是假的,由于在执行过程当中CPU中只有一个线程在执行。
当你使用多进程时,你的效率是高于多线程的。
Python GIL常常被认为是一个神秘而困难的话题,可是请记住做为一名Python支持者,只有当您正在编写C扩展或者您的程序中有计算密集型的多线程任务时才会被GIL影响。
写在后面
这是咸鱼的第三篇python学习笔记,上次的推文给你们送了实战的干货,接下来我会再整理下云盘中的资源,争取给你们再送波补贴。
推荐阅读


咸鱼普拉思
一只咸鱼在编程路上的摸爬滚打,记录摸索中的点点滴滴。
本文分享自微信公众号 - 咸鱼学Python(xianyuxuepython)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。