进程就是程序执行的载体。
进程的应用:我们打开的每个软件、游戏,执行的每一个Python脚本都是启动一个进程。
每一个进程像人一样需要吃饭,它的粮食就是CPU和内存。
同时启动多个软件(进程),多个进程同时在执行程序,他们之间互不干扰,各自执行自己的业务逻辑。
多进程与并行的概念:
通过进程模块执行的函数无法获取返回值。
多个进程同时修改文件可能会出现错误。
进程数量太多可能会导致资源不足,甚至死机的情况。
multiprocessing模块
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
Process | 创建一个进程 | target,args(元组) | 进程对象 |
start | 执行进程 | 无 | 无 |
join | 阻塞程序 | 无 | 无 |
kill | 杀死进程 | 无 | 无 |
is_alive | 进程是否存活 | 无 | bool |
示例:
# coding:utf-8
import time
import os
import multiprocessing
def work_a():
for i in range(10):
print(i, 'a', os.getpid())
time.sleep(1)
def work_b():
for i in range(10):
print(i, 'b', os.getpid())
time.sleep(1)
if __name__ == '__main__':
start = time.time() # 主进程1
a_p = multiprocessing.Process(target=work_a) # 子进程1
# a_p.start() # 子进程1执行
# a_p.join()
b_p = multiprocessing.Process(target=work_b) # 子进程2
# b_p.start() # 子进程2执行
for p in (a_p, b_p):
p.start()
# for p in (a_p, b_p):
# p.join()
for p in (a_p, b_p):
print(p.is_alive())
print('时间消耗是:', time.time() - start) # 主进程代码2
print('parent pid is %s' % os.getpid()) # 主进程代码3行
运行结果:work_a和word_b同时执行,执行完毕后才会执行下面的程序,如时间消耗等。
如果不加join的运行结果:先执行“时间消耗”,再执行子进程a和b。
如果先执行start work_a,然后阻塞join,再执行start work_b的效果
import time
import os
import multiprocessing
def work_a():
for i in range(10):
print(i, 'a', os.getpid())
time.sleep(1)
def work_b():
for i in range(10):
print(i, 'b', os.getpid())
time.sleep(1)
if __name__ == '__main__':
start = time.time() # 主进程1
a_p = multiprocessing.Process(target=work_a) # 子进程1
a_p.start() # 子进程1执行
a_p.join()
b_p = multiprocessing.Process(target=work_b) # 子进程2
b_p.start() # 子进程2执行
print('时间消耗是:', time.time() - start) # 主进程代码2
print('parent pid is %s' % os.getpid()) # 主进程代码3行
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,如果是上百个甚至上千个目标,收订的去创建进程的工作量巨大,此时就可以用到multiprocess模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求。
multiprocessing模块
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
Pool | 进程池创建 | Processcount | 进程池对象 |
apply_async | 任务加入进程池(异步) | func,args | 无 |
join | 等待进程池任务结束 | 无 | 无 |
close | 关闭进程池 | 无 | 无 |
示例:
import os
import time
import multiprocessing
def work(count):
print(count, os.getpid())
time.sleep(5)
if __name__ == '__main__':
pool = multiprocessing.Pool(5) #进程池中有5个进程
for i in range(20):
pool.apply_async(func=work, args=(i,))
time.sleep(20)
运行结果:由结果看出,每个进程的id都不同,也有重复的,进程池中的进程反复使用,不会被关闭。会先执行5个,过了5s后再执行5个。
import os
import time
import multiprocessing
def work(count):
print(count, os.getpid())
time.sleep(5)
return 'result is %s, pid is %s' % (count, os.getpid())
if __name__ == '__main__':
pool = multiprocessing.Pool(5) #进程池中有5个进程
results = []
for i in range(20):
result = pool.apply_async(func=work, args=(i,))
results.append(result)
for res in results:
print(res.get())
# pool.close()
# pool.join()
结果:
0 9048
1 14480
2 13372
3 6764
4 1488
5 9048
result is 0, pid is 9048
6 14480
result is 1, pid is 14480
7 13372
result is 2, pid is 13372
8 6764
result is 3, pid is 6764
9 1488
result is 4, pid is 1488
10 9048
result is 5, pid is 9048
result is 6, pid is 14480
11 14480
12 13372
result is 7, pid is 13372
13 6764
result is 8, pid is 6764
14 1488
result is 9, pid is 1488
15 9048
result is 10, pid is 9048
16 14480
result is 11, pid is 14480
17 13372
result is 12, pid is 13372
18 6764
result is 13, pid is 6764
19 1488
result is 14, pid is 1488
result is 15, pid is 9048
result is 16, pid is 14480
result is 17, pid is 13372
result is 18, pid is 6764
result is 19, pid is 1488
进程锁的加锁与解锁:
from multiprocessing import Process,Lock
manage = Manager()
lock = manage.Lock()
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
acquire | 上锁 | 无 | 无 |
release | 开锁(解锁) | 无 | 无 |
示例:
# coding:utf-8
import os
import time
import multiprocessing
def work(count, lock):
lock.acquire()
print(count, os.getpid())
time.sleep(5)
lock.release()
return 'result is %s, pid is %s' % (count, os.getpid())
if __name__ == '__main__':
pool = multiprocessing.Pool(5) #进程池中有5个进程
manger = multiprocessing.Manager()
lock = manger.Lock()
results = []
for i in range(20):
result = pool.apply_async(func=work, args=(i, lock))
# results.append(result)
# for res in results:
# print(res.get())
pool.close()
pool.join()
结果发现每次只有一个进程在执行。
队列的创建 multiprocessing
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
Queue | 队列的创建 | mac_cout | 队列对象 |
put | 信息放入队列 | message | 无 |
get | 获取队列信息 | 无 | str |
示例:
import time
import json
import multiprocessing
class Work(object):
def __init__(self, q):
self.q = q
def send(self, message):
if not isinstance(message, str):
message = json.dumps(message)
self.q.put(message)
def receive(self):
while 1:
result = self.q.get()
try:
res = json.loads(result)
except:
res = result
print('recv is %s' % res)
if __name__ == '__main__':
q = multiprocessing.Queue()
work = Work(q)
send = multiprocessing.Process(target=work.send, args=({'name': '小慕'},))
recv = multiprocessing.Process(target=work.receive)
send.start()
recv.start()
结果:recv is {'name': '小慕'}
但是程序并没有退出,这是因为程序并不知道该何时退出。在末尾加上recv.terminate()
即可。
# coding:utf-8
import time
import json
import multiprocessing
class Work(object):
def __init__(self, q):
self.q = q
def send(self, message):
if not isinstance(message, str):
message = json.dumps(message)
self.q.put(message)
def send_all(self):
for i in range(20):
self.q.put(i)
time.sleep(1)
def receive(self):
while 1:
result = self.q.get()
try:
res = json.loads(result)
except:
res = result
print('recv is %s' % res)
if __name__ == '__main__':
q = multiprocessing.Queue()
work = Work(q)
send = multiprocessing.Process(target=work.send, args=({'name': '小慕'},))
recv = multiprocessing.Process(target=work.receive)
send_all_p = multiprocessing.Process(target=work.send_all)
send_all_p.start()
send.start()
recv.start()
send_all_p.join()
recv.terminate()
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
进程提供线程执行程序的前置要求,线程在重组的资源配备下,去执行程序。
Python3 线程中常用的两个模块为:
thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 “_thread”。
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
调用这个构造函数时,必需带有关键字参数。参数如下:
Thread类的方法:
方法名 | 说明 | 用法 |
---|---|---|
start | 启动线程 它在一个线程里最多只能被调用一次。它安排对象的 run() 方法在一个独立的控制进程中调用。 |
start() |
run | 线程活动 | run() |
join | 阻塞直到线程执行结束 这会阻塞调用这个方法的线程,直到被调用 join() 的线程终结 – 不管是正常终结还是抛出未处理异常 – 或者直到发生超时,超时选项是可选的。 一定要在 join() 后调用 is_alive() 才能判断是否发生超时 – 如果线程仍然存活,则 join() 超时。 一个线程可以被 join() 很多次 |
join(timeout=None) |
getName | 获取线程的名字 | getName() |
setName | 设置线程的名字 | setName(name) |
is_alive | 判读线程是否存活 | is_alive() |
setDaemon | 守护线程 | setDaemon(True) |
创建 Thread 对象有 2 种手段。
1.直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
线程名字:thread = threading.Thread(target=test,name='TestThread')
import threading
import time
def test():
for i in range(5):
print('test ',i)
time.sleep(1)
thread = threading.Thread(target=test)
thread.start()
for i in range(5):
print('main ', i)
time.sleep(1)
运行结果如下:在主线程上打印5次,在子线程上打印5次。
test 0
main 0
main 1
test 1
main 2
test 2
main 3
test 3
main 4
test 4
2.编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。
import threading
import time
class TestThread(threading.Thread):
def __init__(self,name=None):
threading.Thread.__init__(self,name=name)
def run(self):
for i in range(5):
print(threading.current_thread().name + ' test ', i)
time.sleep(1)
thread = TestThread(name='TestThread')
thread.start()
for i in range(5):
print(threading.current_thread().name+' main ', i)
print(thread.name+' is alive ', thread.isAlive())
time.sleep(1)
线程的问题:
通过线程执行的函数无法获取返回值。
多个线程同时修改文件可能造成数据错乱。
如果要达到,MainThread 结束,子线程也立马结束:
只需要在子线程调用 start() 方法之前设置 daemon 就好了。
也可以在子线程的构造器中传递 daemon 的值为 True。
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
线程可以分为:
创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
示例:
import time
import random
import threading
lists = ['python', 'django', 'tornado',
'flask', 'bs5', 'requests', 'uvloop'
]
new_lists = []
def work():
if len(lists) == 0:
return
data = random.choice(lists)
lists.remove(data)
new_data = '%s_new' % data
new_lists.append(new_data)
time.sleep(1)
if __name__ == '__main__':
start = time.time()
for i in range(len(lists)):
work()
print('old list:', lists)
print('new list:', new_lists)
print('time is %s' % (time.time() - start))
# coding:utf-8
import time
import random
import threading
lists = ['python', 'django', 'tornado',
'flask', 'bs5', 'requests', 'uvloop'
]
new_lists = []
def work():
if len(lists) == 0:
return
data = random.choice(lists)
lists.remove(data)
new_data = '%s_new' % data
new_lists.append(new_data)
time.sleep(1)
if __name__ == '__main__':
start = time.time()
# print('old list len:', len(lists))
t_list = []
for i in range(len(lists)):
t = threading.Thread(target=work)
t_list.append(t)
t.start()
for t in t_list:
t.join()
print('old list:', lists)
print('new list:', new_lists)
# print('new_list len', len(new_lists))
print('time is %s' % (time.time() - start))
concurrent包
方法名 | 说明 | 用法 |
---|---|---|
futures.ThreadPoolExecutor | 创建线程池 | tpool = ThreadPoolExecutor(max_workers) |
submit | 往线程池中加任务 | submit(target, args) |
done | 线程池中的某个线程是否完成了任务 | done() |
result | 获取当前线程执行任务的结果 | result(name) |
示例:
# coding:utf-8
import time
import os
import threading
from concurrent.futures import ThreadPoolExecutor
def work(i):
print(i)
time.sleep(1)
if __name__ == '__main__':
t = ThreadPoolExecutor(2)
for i in range(10):
t.submit(work, (i, ))
# coding:utf-8
import time
import os
import threading
from concurrent.futures import ThreadPoolExecutor
lock = threading.Lock()
def work(i):
lock.acquire()
print(i, os.getpid())
time.sleep(1)
lock.release()
# return 'result %s' % i
if __name__ == '__main__':
t = ThreadPoolExecutor(2)
for i in range(20):
t.submit(work, (i, ))
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步。
请求锁定 — 进入锁定池等待 — — 获取锁 — 已锁定— — 释放锁
Lock包含两种状态——锁定和非锁定。
方法:
当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError 异常。
对于Lock对象而言,如果一个线程连续两次release,使得线程死锁。所以Lock不常用,一般采用Rlock进行线程锁的设定。
RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。
构造方法:mylock = Threading.RLock()
实例方法:
示例:
import threading
mylock = threading.RLock()
num = 0
class WorkThread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.t_name = name
def run(self):
global num
while True:
mylock.acquire()
print('\n%s locked, number: %d' % (self.t_name, num))
if num >= 2:
mylock.release()
print('\n%s released, number: %d' % (self.t_name, num))
break
num += 1
print('\n%s released, number: %d' % (self.t_name, num))
mylock.release()
def test():
thread1 = WorkThread('A-Worker')
thread2 = WorkThread('B-Worker')
thread1.start()
thread2.start()
if __name__ == '__main__':
test()
结果:
A-Worker locked, number: 0
A-Worker released, number: 1
A-Worker locked, number: 1
A-Worker released, number: 2
A-Worker locked, number: 2
A-Worker released, number: 2
B-Worker locked, number: 2
B-Worker released, number: 2
注意:如果使用RLock,那么acquire和release必须成对出现。
条件变量总是与某种类型的锁对象相关联,锁对象可以通过传入获得,或者在缺省的情况下自动创建。当多个条件变量需要共享同一个锁时,传入一个锁很有用。锁是条件对象的一部分,你不必单独地跟踪它。
class threading.Condition(lock=None)
实现条件变量对象的类。一个条件变量对象允许一个或多个线程在被其它线程所通知之前进行等待。
参数:如果给出了非 None 的 lock 参数,则它必须为 Lock 或者 RLock 对象,并且它将被用作底层锁。否则,将会创建新的 RLock 对象,并将其用作底层锁。
方法:
信号量通常用于保护数量有限的资源,例如数据库服务器。在资源数量固定的任何情况下,都应该使用有界信号量。在生成任何工作线程前,应该在主线程中初始化信号量。
class threading.Semaphore(value=1)
该类实现信号量对象。信号量对象管理一个原子性的计数器,代表 release() 方法的调用次数减去 acquire() 的调用次数再加上一个初始值。如果需要, acquire() 方法将会阻塞直到可以返回而不会使得计数器变成负数。在没有显式给出 value 的值时,默认为1。
可选参数 value 赋予内部计数器初始值,默认值为 1 。如果 value 被赋予小于0的值,将会引发 ValueError 异常。
# 在生成任何工作线程前,应该在主线程中初始化信号量
maxconnections = 5
# ...
pool_sema = BoundedSemaphore(value=maxconnections)
# 工作线程生成后,当需要连接服务器时,这些线程将调用信号量的 acquire 和 release 方法:
with pool_sema:
conn = connectdb()
try:
# ... use connection ...
finally:
conn.close()
有界信号量:
class threading.BoundedSemaphore(value=1)
该类实现有界信号量。有界信号量通过检查以确保它当前的值不会超过初始值。如果超过了初始值,将会引发 ValueError 异常。在大多情况下,信号量用于保护数量有限的资源。如果信号量被释放的次数过多,则表明出现了错误。没有指定时, value 的值默认为1。
这是线程之间通信的最简单机制之一:一个线程发出事件信号,而其他线程等待该信号。
class threading.Event
实现事件对象的类。事件对象管理一个内部标志,调用 set() 方法可将其设置为true。调用 clear() 方法可将其设置为false。调用 wait() 方法将进入阻塞直到标志为true。这个标志初始时为false。
此类表示一个操作应该在等待一定的时间之后运行 — 相当于一个定时器。 Timer 类是 Thread 类的子类,因此可以像一个自定义线程一样工作。
class threading.Timer(interval, function, args=None, kwargs=None)
创建一个定时器,在经过 interval 秒的间隔事件后,将会用参数 args 和关键字参数 kwargs 调用 function。如果 args 为 None (默认值),则会使用一个空列表。如果 kwargs 为 None (默认值),则会使用一个空字典。
调用 start() 方法启动定时器。而 cancel() 方法可以停止计时器(在计时结束前), 定时器在执行其操作之前等待的时间间隔可能与用户指定的时间间隔不完全相同。
def hello():
print("hello, world")
t = Timer(30.0, hello)
t.start() # after 30 seconds, "hello, world" will be printed
栅栏类提供一个简单的同步原语,用于应对固定数量的线程需要彼此相互等待的情况。线程调用 wait() 方法后将阻塞,直到所有线程都调用了 wait() 方法。此时所有线程将被同时释放。
栅栏对象可以被多次使用,但进程的数量不能改变。
class threading.Barrier(parties, action=None, timeout=None)
创建一个需要 parties 个线程的栅栏对象。如果提供了可调用的 action 参数,它会在所有线程被释放时在其中一个线程中自动调用。 timeout 是默认的超时时间,如果没有在 wait() 方法中指定超时时间的话。
b = Barrier(2, timeout=5)
def server():
start_server()
b.wait()
while True:
connection = accept_connection()
process_server_connection(connection)
def client():
b.wait()
while True:
connection = make_connection()
process_client_connection(connection)
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 模块中的常用方法:
示例:
#!/usr/bin/python3
import queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, q):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.q = q
def run(self):
print ("开启线程:" + self.name)
process_data(self.name, self.q)
print ("退出线程:" + self.name)
def process_data(threadName, q):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty():
data = q.get()
queueLock.release()
print ("%s processing %s" % (threadName, data))
else:
queueLock.release()
time.sleep(1)
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1
# 创建新线程
for tName in threadList:
thread = myThread(threadID, tName, workQueue)
thread.start()
threads.append(thread)
threadID += 1
# 填充队列
queueLock.acquire()
for word in nameList:
workQueue.put(word)
queueLock.release()
# 等待队列清空
while not workQueue.empty():
pass
# 通知线程是时候退出
exitFlag = 1
# 等待所有线程完成
for t in threads:
t.join()
print ("退出主线程")
结果:
开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出线程:Thread-3
退出线程:Thread-2
退出线程:Thread-1
退出主线程
有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。Python 解释器的垃圾回收线程就是典型的后台线程。如果所有的前台线程都死亡了,那么后台线程会自动死亡。
创建后台线程有两种方式:
daemon
:一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False。
示例:
import threading
# 定义后台线程的线程执行体与普通线程没有任何区别
def action(max):
for i in range(max):
print(threading.current_thread().name + " " + str(i))
t = threading.Thread(target=action, args=(100,), name='后台线程')
# 将此线程设置成后台线程
# 也可在创建Thread对象时通过daemon参数将其设为后台线程
t.daemon = True
# 启动后台线程
t.start()
for i in range(10):
print(threading.current_thread().name + " " + str(i))
# -----程序执行到此处,前台线程(主线程)结束------
# 后台线程也应该随之结束
GIL的作用:单一CPU工作,线程安全,pypy,多进程+多线程
异步是轻量级的线程,又称协程。
异步可以获取异步函数的返回值。主进程需要异步才能使用。
安装:pip install gevent
Micorsoft Visual C++
pip install wheel
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
spawn | 创建协程对象 | Func,args | 协程对象 |
joinall | 批量处理协程对象 | [spawnobj] | [spawnobj] |
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
get(value) | 获取异步程序结果 | 无 | 函数的返回值 |
join | 阻塞等待异步程序结束 | 无 | 无 |
kill | 杀死当前协程 | 无 | 无 |
dead | 判断协程是否消亡 | 无 | bool |
async定义异步
async def test():
return 'a'
await执行异步
async def handle():
result = await test()
函数名 | 功能 | 参数 | 返回值 |
---|---|---|---|
gather | 将异步函数批量执行 | asyncfunc... | List 函数的返回结果 |
run | 执行主异步函数 | [task] | 执行函数的返回结果 |
示例:
async def main():
result = await asyncio.gather(
a(),
b()
)
print(result)
if __name__ = '__main__':
asyncio.run(main())
https://docs.python.org/zh-cn/3.6/library/threading.html?highlight=%E7%BA%BF%E7%A8%8B