1、线程
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的使用。
一个程序运行起来后,一定有个执行代码的东西,这个东西称之为线程。
1.1 使用threading创建线程
单线程执行
import time
def sing():
print("----singing----")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
sing()
多线程执行
import threading
import time
def sing():
print("----singing----")
time.sleep(1)
def dance():
print("----dancing----")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t1 = threading.Thread(target = sing)# 创建对象
t2 = threading.Thread(target = dance)
t1.start() # 创建并启动线程
t2.start()
1.2查看运行的线程、主线程等
threading.enumerate() 返回所有线程。
线程执行顺序不确定,在缺少延时的情况下,输出不确定。
import threading
import time
def sing():
print("----singing----")
time.sleep(1)
def dance():
print("----dancing----")
time.sleep(1)
def main():
t1 = threading.Thread(target = sing)
t2 = threading.Thread(target = dance)
t1.start()
t2.start()
print(threading.enumerate())
# 主线程结束,子线程也会结束,默认主线程会等待
if __name__ == "__main__":
main()
1.3 通过继承thread类完成线程创建
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
print("----runing-----")
if __name__ == "__main__":
t = MyTheard()
t.start() # 自动调用类中的run方法
1.4 多线程共享全局变量
PS:函数内部修改全局变量的时候,若修改变量名指向,需要加global,不修改指向时不需要 。会出现资源竞争,引起数据错误。
1.5互斥锁解决资源竞争问题
死锁,在线程共享多个资源的时候,如果两个线程分别占有一部分资源并同时等待对方的资源,就会造成死锁。
避免死锁的方式,尽量避免或者添加超时时间。
# 创建锁
mutex = threading.Lock() # 默认没上锁
# 锁定
mutex.acquire()
# 释放
mutex.release()
# 注:之前没上锁,调用acquire不会堵塞
# 若调用acquire前已经被其他线程上锁,此时会堵塞,直到这个锁解锁为止。
import threading
import time
g_num = 0
def test1():
for i in range(10):
# 若已上锁,则暂停,未上锁则上锁
mutex.acquire()
g_num += 1
mutex.release()
print('----test1%d----' % g_num)
def test2():
for i in range(10):
mutex.acquire()
g_num += 1
mutex.release()
print('----test2%d----' % g_num)
mutex = threading.Lock()
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t1.start()
print("----%d----" % g_num)
1.6 聊天器案例
同时首收发信息
import socket
import threading
def recv_msg(udp_socket):
while True:
recv_data = udp_socket.recvfrom(1024)
print(recv_data)
def send_msg(udp_socket):
while True:
send_data = input("输入要发送的数据:")
udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
def main():
# 1、创建套接字
udp_socket = socket.socket(socket.AF_NET, socket.SOCK_DGRAM)
# 2、绑定本地信息
udp_socket.bind("", 7890)
# 3、获取目的地址
dest_ip = input("请输入IP:")
dest_port = input("请输入:")
# 4、创建两个线程,去执行相应的功能
t_recv = threading.Thread(target = recv_msg, args = (udp_socket,)
t_send = threading.Thread(target = send_msg, args = (udp_socket, dest_ip, dest_port)
t_recv.start()
t_send.start()
if __name__ == "__main__":
main()
2 进程
通俗理解,运行起来的程序(代码+资源)就是进程,是系统分配资源的基本单元。一个程序可以有多个进程。
进程是系统资源分配的单位,线程是系统调度执行的单位。
线程不能单独执行,必须依存在进程中。
进程之间相互独立。
2.1 引用Process
import multiprocessing
import time
def test1():
while True:
print("1----")
time.sleep(1)
def test2():
while True:
print("2----")
time.sleep(1)
def main():
p1 = multiprocessing.Process(target = test1)
p2 = multiprocessing.Process(target = test2)
p1.start()
p2.start()
if __name__ == "__main__":
main()
# linux中用ps -aux 显示所有仅从
# kill PID 关闭线程
2.2 进程之间的通信
使用消息队列进行通信,一个负责存入数据,一个负责处理数据。
import multiprocessing
def download():
data = [11, 22, 33, 44]
for temp in data:
q.put(temp)
print("下载完毕")
def analysis():
data_list = list()
while True:
data = q.get()
data_list.append(data)
if q.empty():
break
print(data_list)
def main():
q = mutltiprocessing.Queue()
p1 = multiprocessing.Process(target = download)
p2 = muiltiprocessing.Process(target = analysis)
p1.start()
p2.start()
if __name__ == "__main__":
main()
2.3 进程池
创建进程需要耗费大量的资源,当需要创建的子进程数量太多时,可以用Pool方法。
Pool初始化时可以指定最大进程数,并预先创建,不会销毁和重建,重复利用进程。
from multiprocessing import pool
import os, time, random
def woeker(msg):
t_start = time.time()
print("%s开始执行,进程号%d" % (msg, os.getpid())
time.sleep(random.random()*2)
t_stop = time.time()
print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))
def main():
po = pool(3)
# Pool.apply_async(要调用的目标,(传递给目标的参数元组))
for i in range(0, 10):
po.apply_async(worker,(i, ))
print("---start---")
po.close() # 关闭进程不允许再添加任务
po.join() # 主线程不会等待线程池,添加join()等待执行完毕,必须再close后面
print("---end---")
if __name__ == "__main__";
main()
3 协程
3.1 迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。节省内存空间。
可迭代对象:列表、元组、字符串等。
1、判断obj是否是可以迭代的 isinstance(a, Iterable);
2、在第一步返回True的情况下,调用iter函数,得到obj对象的__iter__返回值 a_itreator=iter(a),a中定义了__iter__和__next__。
(iter__返回的值是一个迭代器)
3、调用__next,next(a_iterator)
# 验证是否可迭代
from collections import Iterable
from collections import Iterator
isinstance(a, Iterable) # 判断对象是否可迭代,Ture为可迭代
a_iterator = iter(a)
isinstance(a_iterator, Iterator)
在类中定义__iter__即可使用for循环。
import time
class Classmate():
def __init__(self):
self.names = list()
self.current_num = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
return self
def __next__(self):
if self.current_num < len(self.names):
ret = self.names[self.current_num]
current_num += 1
return ret
else:
raise StopIteration
3.2 生成器
一类特殊的迭代器。
创建生成器的方法一:
将列表生成式的 [] 改成 () .用的少。
nums = [x for x in range(10)]
# 返回列表
nums = (x for x in range(10))
# 返回生成器
for num in nums:
print(num)
创建方法二:
如果一个函数中有yield语句,bane这个就不是函数,而是一个生成器的模板。原本的调用函数改成创建生成器对象。
def fib(all_nums):
a, b = 0, 1
current_num = 0
while (current_num < all_nums):
yield a
a, b = b, a+b
current_num += 1
return '最终返回值'
f = fib(10)
while True:
try:
res = next(f) # 0
print(res)
except Exception as res:
print(res.value) # 输出最终return值
break
使用send启动生成器。
def fib(all_nums):
a, b = 0, 1
current_num = 0
while (current_num < all_nums):
rcv = yield a
print("--->", rcv)
a, b = b, a+b
current_num += 1
return '最终返回值'
f = fib(10)
res = next(f) # 0
res = f.send('haha') # rcv='haha',执行print, res=1,
使用yield实现多任务
import time
def test1():
while True:
print("---1---")
yield
def test2():
while True:
print("----2----")
yield
def main():
t1 = test1() # 生成器1
t2 = test2() # 生成器2
while True:
# 交替执行,实现并发
next(t1) # 调用任务一,停在yield处
next(t2) # 调用任务二,停在yield处
if __name__ == "__main__":
main()
3.3 greenlet
为了更好使用协程来完成多任务,python使用greenlet对其封装,从而使得切换任务变得更加简单。
from greenlet import greenlet
import time
def test1():
while True:
print("---test1----")
gr2.switch() # 运行tes2
time.sleep(0.5)
def test2():
while True:
print("----test2----")
gr1.switch() # 运行特殊1
time.sleep(0.5)
gr1 = test1()
gr2 = test2()
gr1.switch() # 运行test1
3.4 gevent
python中的一个更加强大的并且自动切换任务的模块
import gevent
import time
from gevent import monkey
# 将外部模块处理成gevent内部模块,time等
monkey.patch_all()
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5) # gevent.sleep()
gevent.joinall([
# 遇到sleep就接着往下执行,
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)])
4 进程、线程、协程对比
工厂中生成某种商品。
花费一些物力财力制作一条生产线,这个生产线上所有的器材材料这些所有的为了生产准备的资源称之为:进程
只有生产线市不能生产的,所有老板请来了工人,做事的工人称之为:线程
为了提高生产率,想到三种方式:
1、多招工人,即单进程,多线程方式
2、生产线资源有限,工人不是越多越好,所以选择再开辟一条生产线,即多进程多线程的方式
3、老板规定,如果某个员工临时没事,或在等待状态,就利用这个时间去做其他事,即协程方式。
总结
1、进程是资源分配的单位
2、线程是操作系统调度的单位
3、进程切换需要耗费较大资源
4、线程切换资源一般,效率一般
5、协程切换任务资源很小,效率高
6、多线程、多进程根据cpu核数不一样可能是并行的,但是协程是一个线程中,所以是并发