进程
计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到
内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中
的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。
线程
线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并
共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷
你进程”。
让步
线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下
文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫
做让步(yielding)。
单核和多核是如何实现并发的
一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线
程间的信息共享和通信更加容易。线程一般是以并发方式执行的,正是由于这种并行和数据
共享机制,使得多任务间的协作成为可能。当然,在单核 CPU 系统中,因为真正的并发是不
可能的,所以线程的执行实际上是这样规划的:每个线程运行一小会儿,然后让步给其他线
程(再次排队等待更多的 CPU 时间)。在整个进程的执行过程中,每个线程执行它自己特定
的任务,在必要时和其他线程进行结果通信。
当然,这种共享并不是没有风险的。如果两个或多个线程访问同一片数据,由于数据访
问顺序不同,可能导致结果不一致。这种情况通常称为竞态条件(race condition)。幸运的是,
大多数线程库都有一些同步原语,以允许线程管理器控制执行和访问。
守护线程: setDaemon()
import threading
import time
def music():
print("start music %s" % time.ctime())
time.sleep(3)
print("stop music %s" % time.ctime())
def game():
print("start game %s" % time.ctime())
time.sleep(5)
print("stop game %s" % time.ctime()) # 13
if __name__ == "__main__":
t1 = threading.Thread(target=music)
t2 = threading.Thread(target=game)
t1.start()
t2.setDaemon(True) # 将t1设置为守护线程
t2.start()
# t2.setDaemon(True) # 将t1设置为守护线程
"""
守护线程:没有守护线程时,主线程语句执行完后,等待子线程,子线程全部结束后,主线程结束,
有守护线程时,主线程语句执行完后,不等待守护线程,非守护线程结束后,主线程立即结束,
守护线程不论是否结束,都跟随主线程一起结束
"""
print("ending.........")
"""
t2设置为守护线程后,则t1线程结束后,主线程结束,守护线程t2立即结束,13行未被执行
start music Tue Aug 27 08:55:21 2019
start game Tue Aug 27 08:55:21 2019
ending.........
stop music Tue Aug 27 08:55:24 2019
"""
join()方法,会等待线程调用了该方法的线程结束后,在向下继续执行
import threading
import time
def music():
print("start music %s" % time.ctime()) #6
time.sleep(3)
print("stop music %s" % time.ctime()) #8
def game():
print("start game %s" % time.ctime()) #11
time.sleep(5)
print("stop game %s" % time.ctime()) #13
if __name__ == "__main__":
# t1 = threading.Thread(target=music) # 如果需要传参数 ,args=参数
# t2 = threading.Thread(target=game)
# t1.start()
# t2.start()
# print("ending>>>>>>>") #20 # 6,11,20同时执行,3秒后8执行,再过2秒13执行
#jion()方法由实例调用,作用是等待实例执行完后继续执行后面的语句
t1 = threading.Thread(target=music) # 如果需要传参数 ,args=参数
t2 = threading.Thread(target=game)
t1.start()
t2.start()
t2.join()
"""
join()方法,调用此方法的线程必须结束后才会继续向下执行,
也就是等待线程结束后才会继续执行
同时执行6,11,3秒后t1结束,5秒后t2结束,执行34
"""
print("ending>>>>>>>")# 34 # 6,11同时执行,3秒后8执行,再过2秒13,34同时执行
实例的方法
join():等待线程
setDaemon():守护线程
run():线程被cpu调用后自动执行
start():启动线程活动,使处于等待被cpu调用的状态
isAlive():返回线程是否活动
getName():返回线程名,默认:thread-1
setName():设置线程名
threading提供的一些方法
#threading.currentThread():返回当前的线程变量
#threading.enumerate():返回正在运行的线程的列表
#threading.activeCount():返回正在运行的线程数量
GIL:
python的GIL 全局解释器锁,这是在解释器中引入的,简单来讲,就是在一个进程中不允许CPU同时执行多个
线程,哪怕是多核CPU,也只能采用让步的方式轮循
线程执行的任务有两种:1 IO密集型
2 计算密集型
对于计算密集型,因为让步轮循的关系,反而不如串行执行的效率高
但是IO密集型不同,等待IO时其他线程继续执行,节省了等待IO的时间,此时多线程是有意义的
GIL的测试
import threading
import time
def add():
sum = 0
for i in range(10000000):
sum += i
print("sum",sum)
def mul():
sum1 = 1
for i in range(1,100000):
sum1 *= i
print("sum1",sum1)
if __name__ =="__main__":
start = time.time()
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=mul)
l = []
l.append(t1)
l.append(t2)
# for t in l:
# t.start()
#
# for t in l:
# t.join() # cost time: 8.800450
add()
mul() #cost time: 7.661081
print("cost time: %f" % (time.time()-start))
"""
会发现串行执行会比线程执行更快,这就是因为GIL的影响
"""
同步锁:
假设我们要做一个100的累减,通过使用100个线程来实现
import threading
import time
# def cut():
# global num
# num-=1 # out: 0
# def cut():
# global num
# temp = num
# time.sleep(0.001)
# num = temp-1 # time.sleep(0.01): out: 95 96
# time.sleep(0.001): out : 78 82 81
"""
会出现这种现象就是因为每个线程在自己的轮循时间内有阻塞,会立刻切换其他线程,但其他线程拿到的num
并没有被前一个线程改变,阻塞时间足够大的情况下,所有的线程拿到的num都是100,说以最后的结果num = 99
而线程在自己轮循时间内完成了对num的改变,之后的线程拿到的num就都是改变后的了
"""
# num = 100
# l = []
# for i in range(100):
# t = threading.Thread(target=cut)
# t.start()
# l.append(t)
#
#
# for j in l:
# j.join()
#
# print(num) # 0
# 若果想要在线程有阻塞或者,轮循时间内无法完成任务的情况下,实现目的,就要使用同步锁
#同步锁:同步锁之内的语句在未执行完的情况下不允许切换线程
def cut():
global num
lock.acquire() #使用同步锁包围要同步的执行语句 这是类LockType() 中的方法
temp = num
time.sleep(0.001)
num = temp-1
lock.release() #使用同步锁包围要同步的执行语句
num = 100
l = []
lock = threading.Lock() # 创建一个实例LockType()
for i in range(100):
t = threading.Thread(target=cut)
t.start()
l.append(t)
for j in l:
j.join()
print(num) # 0
同步对象
可以实现控制线程的执行顺序
event = threadin.Event
event.wait() 未设置标志位时阻塞,标志位被设置时等同于pass
event.set() 设置标志位
event.clear() 清除标志位
信号量
semaphore=threading.Semaphore(val) # val决定同时启动的线程数量
# 信号量就相当于停车位,停车位满了,后来的车就必须等待,直到车位被释放
举例:
import threading
import time
class Mythread(threading.Thread):
def run(self):
if semaphore.acquire(): #semaphore.acquire() == True
print(self.getName())
time.sleep(3)
semaphore.release()
if __name__ == "__main__":
L = []
semaphore = threading.Semaphore(5)
for i in range(100):
L.append(Mythread())
for j in L:
j.start() # 效果是每隔3秒执行五个线程
队列
import queue
# 队列的三种模式
# 模式一 先进先出
q = queue.Queue() # ,还可以传入参数val=5,表示只允许put进5个元素
"""
[1, 2, 3]
1
wa
(1, 2)
{'name': 'alxe'}
"""
q.put([1,2,3])
q.put(1)
q.put("wa")
q.put((1,2))
q.put({"name":"alxe"})
while True:
date = q.get()
print(date)
#模式二 先进后出
q = queue.LifoQueue()
"""
{'name': 'alxe'}
(1, 2)
wa
1
[1, 2, 3]
"""
q.put([1,2,3])
q.put(1)
q.put("wa")
q.put((1,2))
q.put({"name":"alxe"})
while True:
date = q.get()
print(date)
#模式三 按照优先级
q = queue.Queue() # 按照优先级
"""
[1, 1]
[2, (1, 2)]
[3, 'wa']
[4, {'name': 'alxe'}]
[5, [1, 2, 3]]
"""
q.put([5,[1,2,3]])
q.put([1,1])
q.put([3,"wa"])
q.put([2,(1,2)])
q.put([4,{"name":"alxe"}]) # block = False:当队列为满时,继续传入会报错
while True:
date = q.get(block=False) # block = False :当队列为空时报错
"""
raise Empty
_queue.Empty # 报错,队列已满
"""
print(date)
q = queue.Queue(4) # 按照优先级
"""
[1, 1]
[2, (1, 2)]
[3, 'wa']
[4, {'name': 'alxe'}]
[5, [1, 2, 3]]
"""
q.put([5,[1,2,3]])
q.put([1,1])
q.put([3,"wa"])
q.put([2,(1,2)])
# 队列满了之后,在继续装入数据,会一直等待,直到队列中数据被取出,有空位为止
# q.put([4,{"name":"alxe"}]) # block = False:当队列为满时,继续传入会报错
"""
raise Full
queue.Full # 报错,队列已满
"""
while True:
# 取出全部数据后等待,直到再次有数据被装入
date = q.get() # block = False :当队列为空时报错
"""
raise Empty
_queue.Empty # 报错,队列已空
"""
print(date)
#其他方法
q = queue.Queue(2)
q.put([5,[1,2,3]])
q.put([1,1]) # q.put_nowait() 相当于 q.put("wan",block=False)
print(q.empty()) #队列是否为空
print(q.full()) #队列是否为满
print(q.qsize()) #队列中的数据个数
"""
False
True
2
"""
q.task_done() # 在完成一项工作后,向完成任务的队列发送一个信号,由q.join()接收
q.join() # 实际上是等到列表为空之后在进行其他操作
while True:
date = q.get() # q.get_nowait() 相当于 q.get(block=False)
print(date)
多进程调用:
import multiprocessing
import time
import os
# 调用方式一
def foo(num):
# print(num)
print("%s......%s" % (num,time.ctime()))
if __name__ == "__main__":
L = []
for i in range(3):
p = multiprocessing.Process(target=foo,args=("1",))
L.append(p)
p.start()
for j in L:
j.join()
print("ending........")
# 调用方式二
class Myprocess(multiprocessing.Process):
def __init__(self):
super(Myprocess,self).__init__()
def run(self):
print("%s......%s" % (self.name,time.ctime()))
if __name__ == "__main__":
L = []
for i in range(3):
p = Myprocess()
L.append(p)
p.start()
for j in L:
j.join()
print("ending........")
# 进程关系
class Myprocess(multiprocessing.Process):
def __init__(self):
super(Myprocess,self).__init__()
def run(self):
print("PPID :%s" % (os.getppid())) # 获取进程的父ID
print("PID :%s" % os.getpid())
if __name__ == "__main__":
L = []
print("PPID :%s" % (os.getppid()))
print("PID :%s" % os.getpid())
# for i in range(3):
p = Myprocess()
p2 = Myprocess()
# L.append(p)
p.daemon = True # 守护进程
p.start()
p2.start()
# for j in L:
# j.join()
p.join()
p2.join()
print("ending........")
进程间的通信:
# 进程间通信的三种方式
import multiprocessing
import queue
import time
# 一 通过进程队列(注意它与线程队列的区别),将进程队列当作参数传给子进程
def foo(q):
# q.put("alxe")
date = q.get() # 获取进程队列中数据
# time.sleep(1)
date["name"] = "alxe" # 对数据进行修改
# print(date)
if __name__ == "__main__":
q = multiprocessing.Queue() # 创建一个进程队列
d = {"name": "lhf"}
q.put(d)
print("================")
p = multiprocessing.Process(target=foo,args=(q,))
p.start() # 启动子线程对数据进行修改
# p.daemon(True)
p.join() # 等待子进程完成修改
print(q.get()) # 在主进程中获取队列数据,看是否被修改
# print(q.get())
"""
特别注意:同一进程队列在子进程中被get的数据,在主进程中无法再被获取
"""
# 二 管道 farther.conn,sub.conn = Pipe() 像socket中的通信
def foo(sub_conn):
date = sub_conn.recv()
print(date)
sub_conn.send("nihao")
if __name__ == "__main__":
main_conn,sub_conn = multiprocessing.Pipe()
d = {"name": "alxe"}
p = multiprocessing.Process(target=foo,args=(sub_conn,))
p.start() # 启动子线程对数据进行修改
# p.daemon(True)
main_conn.send(d)
date = main_conn.recv()
print(date)
"""
管道只能用于进程间的数据发送与接收(数据交互)
"""
# 三 数据共享 manager with Manager() as manager:
'''
以上两种方法只实现了数据交互,没有真正实现数据共享
support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore,
Condition, Event, Barrier, Queue, Value and Array.
'''
def foo(d,l):
d["name"] = "alxe"
l.append("wang")
print(id(d),id(l))
if __name__ == "__main__":
with multiprocessing.Manager() as manger:
d = manger.dict()
l = manger.list(range(5))
p = multiprocessing.Process(target=foo,args=(d,l,))
p.start()
p.join()
print(d)
print(l)
print(id(d),id(l))
协程:
# 协程是面向用户的,在线程和进程中,我们无法控制每个线程或进程的执行顺序,它们都通过抢占
# 决定执行顺序,协程就是为了解决这个问题
# 协程是由yield来实现的,这是协程的首先回顾一下
# def foo():
# print("ok1")
# s = yield 4 # 两步操作,1.执行到这一句return 4 2.s = f.send的值
# print(s)
# # print("ok2")
# f = foo()
# # 我们定义了一个生成器(注意不是函数了),要想执行,需要next方法
# #方法一:
# # f.__next__()
# #方法二
# # next(f)
# #方法三
# date = f.__next__() # 可以发送一个参数,由对应的yield接收
# print(date)
# f.send(6) # yield的赋值操作是在执行到yield之后的下一次next中
# 下面再用yield的方法实现之前的生产者消费者模型
# import time
#
# def consumer(n):
# print("顾客 %s 来到包子铺" % n)
# while True:
# m = yield
# print("\033[32;1m顾客%s 吃掉了%s\033[0m" % (n,m))
# def producer(con1,con2):
# next(con1)
# next(con2)
# i = 0
# while True:
# time.sleep(1)
# print("producer 开始做包子 %s %s" % (i,i+1))
# con1.send(i)
# con2.send(i+1)
# i += 2
#
# if __name__ == "__main__":
# con1 = consumer(1)
# con2 = consumer(2)
# producer(con1,con2)
# 用greenlet实现函数间的切换,不需先将函数声明为生成器
# from greenlet import greenlet
# def foo1():
# print(1)
# g2.switch()
# print(2)
#
# def foo2():
# print(3)
# g1.switch()
# print(4)
#
# g1 = greenlet(foo1)
# g2 = greenlet(foo2)
# g1.switch()
# import asyncio
# async def foo1():
# await asyncio.sleep(1)
# print("1")
#
# async def main():
# test1 = asyncio.create_task(foo1())
# test2 = asyncio.create_task(foo1())
# test3 = asyncio.create_task(foo1())
# test4 = asyncio.create_task(foo1())
# test5 = asyncio.create_task(foo1())
# test6 = asyncio.create_task(foo1())
#
# await test1
# await test2
# await test3
# await test4
# await test5
# await test6 # 执行6次也是等待1秒
# asyncio.run(main())
# import asyncio
# async def foo1(i):
# await asyncio.sleep(1)
# print(i)
# loop = asyncio.get_event_loop()
# myfun_list = (foo1(i) for i in range(100))
# print(myfun_list)
# loop.run_until_complete(asyncio.gather(*myfun_list))
# import asyncio
# async def foo1(name):
# await asyncio.sleep(1)
# print(name)
# async def main(foo1):
# myfun_list = (foo1(i) for i in range(100))
# print(myfun_list)
# await asyncio.gather(*myfun_list)
# asyncio.run(main(foo1))
#
# import asyncio
# async def foo1(name):
# await asyncio.sleep(1)
# print(name)
# async def main():
# await asyncio.gather(
# foo1("who"),
# foo1("is"),
# foo1("alxe"),
# )
# asyncio.run(main())
def f(i):
print(i)
(f(i) for i in range(1)).__next__()
(f(i) for i in range(1)).__next__()
进程池和线程池:
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import requests
"""
多进程和多线程在代码上只是生成的pool不同,一个进程执行时,系统会为其分配必要的资源(地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据),
当一个进程下有多个线程时,因为GIL锁(全局解释器锁)的限制,线程在执行时只能依次(或让步)由同一CPU执行,
此时如果线程执行的是计算任务,由于计算必须由CPU计算,所以线程只能依次执行,多线程变得没有意义
这样计算密集的任务就应该由多进程执行,它不受GIL锁的限制
IO密集型的任务就使用多线程,因为询问CPU几乎不耗时
"""
pool = ThreadPoolExecutor(3)
url_list = [
"https://www.baidu.com",
"https://www.zhihu.com",
"https://www.bilibili.com/",
"https://www.cnblogs.com/",
"https://www.iconfont.cn/collections/index?spm=a313x.7781069.1998910419.7&type=3",
"https://www.bootcss.com/",
"http://jquery.cuishifeng.cn/"
]
def visit_url(url):
response = requests.request(
method="GET",
url=url,
headers={'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
)
print(url,response.status_code)
return response
def done_callback(response,*args,**kwargs):
print(response)
for url in url_list:
v = pool.submit(visit_url,url)
v.add_done_callback(done_callback)
pool.shutdown(wait=True)