线程&进程
进程
- 一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)
- 一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间),还要有至少一个线程。
- 每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。
- 多进程是可以利用多核cpu的。
线程
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
- 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。
- 线程和线程之间是互相独立的。
线程和进程的关系以及区别
进程和线程的关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程(至少一个)。
- 资源分配给进程,同一进程的所有线程共享该进程的资源。
- 处理机分配给线程。
- 线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体。
进程和线程的区别
- 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
- 并发性:进程之间可以并发执行,同一个进程间的线程也可以并发执行。
- 拥有资源:进程是拥有资源的一个独立单位,线程不拥有资源,但可以访问隶属于进程的资源。
- 系统开销:在创建或者撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或者撤销线程时的开销。
- 运行:进程可以独立运行。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立运行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 地址空间:进程有自己独立的地址空间,而线程们共享进程的地址空间。
多线程
多线程的优点
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
- 程序的运行速度可能加快。
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
python线程模块
python中有两种方式实现线程:
- 实例化一个threading.Thread的对象,并传入一个初始化函数对象(initial function )作为线程执行的入口。
import threading
import time
def clean():
print('打扫卫生')
time.sleep(2)
def xiyifu():
print('洗衣服')
time.sleep(3)
def cook():
print('做饭')
time.sleep(1)
t1 = threading.Thread(target=clean)
t2 = threading.Thread(target=xiyifu)
t3 = threading.Thread(target=cook)
t1.start()
t2.start()
t3.start()
输出结果为:
打扫卫生
洗衣服
做饭
- 继承threading.Thread,并重写run函数;
import threading
import time
class MyThread(threading.Thread):
def __init__(self,num):
threading.Thread.__init__(self)
self.num = num
def run(self):#定义每个线程要运行的函数
print("running on number:%s" %self.num)
time.sleep(3)
if __name__ == '__main__':
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
线程等待
线程等待,多线程在运行的时候,每个线程都是独立运行的,不受其他的线程干扰,如果想在哪个线程运行完之后,再做其他操作的话,就得等待它完成,那怎么等待呢,使用join,等待线程结束。
import threading
import time
def run():
print('qqq')
time.sleep(1)
print('等待')
lis = []
for i in range(5):
t = threading.Thread(target=run)
lis.append(t)
t.start()
for t in lis:
t.join()
print('结束')
输出结果为:
qqq
qqq
qqq
qqq
qqq
等待等待
等待
等待等待
结束
守护线程
如果当前python线程是守护线程,那么意味着这个线程是“不重要”的,“不重要”意味着如果他的主进程结束了但该守护线程没有运行完,守护进程就会被强制结束。如果线程是非守护线程,那么父进程只有等到守护线程运行完毕后才能结束。
在python中,线程通过threadName.setDaemon(True|False)来设置是否为守护线程。
1.先设置非守护线程
import threading
import time
import random
def talk(name):
print('正在和%s聊天' % name)
time.sleep(random.randint(1, 5))
print('和%s聊完了' % name)
t1 = threading.Thread(target=talk, args=['张三'])
t1.setDaemon(False) # 设成非守护线程
t1.start()
t2 = threading.Thread(target=talk, args=['李四'])
t2.setDaemon(False) # 设成非守护线程
t2.start()
t3 = threading.Thread(target=talk, args=['王五'])
t3.setDaemon(False) # 设成非守护线程
t3.start()
print('退出聊天')
输出结果为:
正在和张三聊天
正在和李四聊天
正在和王五聊天退出聊天
和李四聊完了
和张三聊完了
和王五聊完了
可以看到,在主线程退出聊天后子线程还在继续执行。
2.然后设置成守护线程
import threading
import time
import random
def talk(name):
print('正在和%s聊天' % name)
time.sleep(random.randint(1, 5))
print('和%s聊完了' % name)
t1 = threading.Thread(target=talk, args=['张三'])
t1.setDaemon(True) # 设成非守护线程
t1.start()
t2 = threading.Thread(target=talk, args=['李四'])
t2.setDaemon(True) # 设成非守护线程
t2.start()
t3 = threading.Thread(target=talk, args=['王五'])
t3.setDaemon(True) # 设成非守护线程
t3.start()
print('退出聊天')
输出结果为:
正在和张三聊天
正在和李四聊天
正在和王五聊天退出聊天
可以看到主线程结束掉之后子线程也跟着结束了。
线程锁
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程加锁。
1.先看下不加锁的情况
import threading
count = 0
lock = threading.Lock()
def add():
global count
print(threading.current_thread(),'开始运行')
for i in range(1000000):
count+=1
for i in range(2):
t = threading.Thread(target=add)
t.start()
while threading.active_count()!=1:
pass
print(count)
输出结果为:
<Thread(Thread-1, started 14388)> 开始运行
<Thread(Thread-2, started 11884)> 开始运行
1550943
可以看到两个线程同时计算count+=1时会导致最后的结果不是预期的结果,并且每次执行结果还会变化。
2.然后再看加锁的情况
import threading
count = 0
lock = threading.Lock()
def add():
global count
print(threading.current_thread(),'开始运行')
for i in range(1000000):
lock.acquire() # 加锁
count+=1
lock.release() # 解锁
for i in range(2):
t = threading.Thread(target=add)
t.start()
while threading.active_count()!=1:
pass
print(count)
输出结果为:
<Thread(Thread-1, started 10556)> 开始运行
<Thread(Thread-2, started 14220)> 开始运行
2000000
加了锁之后结果就正常了。
队列
queue是python的标准库,可以直接import引用。
在python中,多个线程之间的数据是共享的,多个线程进行数据交换的时候,不能够保证数据的安全性和一致性,所以当多个线程需要进行数据交换的时候,队列就出现了,队列可以完美解决线程间的数据交换,保证线程间数据的安全性和一致性(简单的来说就是多线程需要加锁,很可能会造成死锁,而queue自带锁。所以多线程结合queue会好的很多。
import queue
import random
import time
import threading
# 生产者/消费者模式
# 生产者
def producer(num):
for i in range(num):
order_id = random.randint(1, 99999)
print('订单生成,orderid:%s' % order_id)
# 把生成的订单添加到队列里面
orders_q.put(order_id)
time.sleep(1)
# 消费者1
def consumer():
while True:
# 获取队列里面的订单号
oreder_id = orders_q.get()
if oreder_id is None:
break
print('consumer1,订单落库', oreder_id)
# 消费者2
def consumer2():
while True:
# 获取队列里面的订单号
oreder_id = orders_q.get()
if oreder_id is None:
break
print('consumer2,订单落库', oreder_id)
# 创建队列
orders_q = queue.Queue()
# 创建多线程
# 生产者生成10条订单
t1 = threading.Thread(target=producer, args=(10,))
t1.start()
t2 = threading.Thread(target=consumer)
t2.start()
t3 = threading.Thread(target=consumer2)
t3.start()
# 阻塞生产者子线程,生产者子线程结束后才执行orders_q.put() 方法
t1.join()
# 为了确保生产者生产完所有数据后,最后一个是None,方便结束消费者子线程中的while循环,否则会一直等待队列中加入新数据。
orders_q.put(None)
orders_q.put(None)
输出结果为:
订单生成,orderid:61819
consumer1,订单落库 61819
订单生成,orderid:45077
consumer1,订单落库 45077
订单生成,orderid:55182
consumer2,订单落库 55182
订单生成,orderid:98005
consumer1,订单落库 98005
订单生成,orderid:40798
consumer2,订单落库 40798
订单生成,orderid:4290
consumer1,订单落库 4290
订单生成,orderid:35392
consumer2,订单落库 35392
订单生成,orderid:83016
consumer1,订单落库 83016
订单生成,orderid:29978
consumer2,订单落库 29978
订单生成,orderid:52625
consumer1,订单落库 52625
使用Queue组件实现的缺点就是,实现了多少个消费者consumer线程,就需要在最后往队列中添加多少个None标识,方便生产完毕结束消费者consumer进程。否则,orders_q.get() 不到任务会阻塞子进程,因为while循环,直到队列orders_q中有新的任务加进来,才会再次执行。而我们的生产者只能生产这么多东西,所以相当于程序卡死。
线程池
- 系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
- 线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。
- 使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。
下面实现一个利用线程池下载图片的方法:
import threadpool
import requests
import threading
import hashlib
import os
def down_load_pic(url):
print(threading.current_thread())
print('开始下载',url)
r = requests.get(url)
# 把下载的图片名称用MD5加密
msg = str(url)
m = hashlib.md5(msg.encode())
result = m.hexdigest()
file_name = result + '.jpg'
with open(os.path.join('imgs',file_name),'wb') as fw:
fw.write(r.content)
urls = ['http://a0.att.hudong.com/30/29/01300000201438121627296084016.jpg','http://a4.att.hudong.com/22/59/19300001325156131228593878903.jpg','http://a2.att.hudong.com/86/10/01300000184180121920108394217.jpg','http://a1.att.hudong.com/05/00/01300000194285122188000535877.jpg','http://a4.att.hudong.com/25/99/19300000421423134190997943822.jpg']
# 创建线程池,设置最大连接数为10
pool = threadpool.ThreadPool(10)
#让它给每个线程分配数据
reqs = threadpool.makeRequests(down_load_pic,urls)
for req in reqs:
pool.putRequest(req) #开始运行起来
pool.wait() #等待子线程执行结束
print('运行完成')
输出结果为:
<WorkerThread(Thread-1, started daemon 5988)><WorkerThread(Thread-2, started daemon 14012)>
开始下载 <WorkerThread(Thread-5, started daemon 5976)>
开始下载<WorkerThread(Thread-4, started daemon 1268)>
开始下载<WorkerThread(Thread-3, started daemon 5184)>
开始下载
http://a4.att.hudong.com/22/59/19300001325156131228593878903.jpg
http://a2.att.hudong.com/86/10/01300000184180121920108394217.jpg
http://a1.att.hudong.com/05/00/01300000194285122188000535877.jpg 开始下载
http://a0.att.hudong.com/30/29/01300000201438121627296084016.jpg
http://a4.att.hudong.com/25/99/19300000421423134190997943822.jpg
运行完成