进程的创建 进程 VS 程序 编写完毕的代码,在没有运行的时候,称之为程序 正在运行着的代码,就成为进程 注意: 进程,除了包含代码以外,还有需要运行的环境等,所以和程序是有区别的 进程的创建 创建子进程: Python的os模块封装了常⻅的系统调用,其中就包括fork,可以在Python程 序中 轻松创建子进程:
01创建子进程
import os
import time
#定义一个全局变量money
money = 100
print("当前进程的pid:", os.getpid())
print("当前进程的父进程pid:", os.getppid())
#time.sleep(115)
p = os.fork()
#子进程返回的是0
if p == 0:
money = 200
print("子进程返回的信息, money=%d" %(money))
#父进程返回的是子进程的pid
else:
print("创建子进程%s, 父进程是%d" %(p, os.getppid()))
print(money)
02多进程编程方法一实例化对象
Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调⽤对象;
args:表示调⽤对象的位置参数元组;
kwargs:表示调⽤对象的关键字参数字典;
name:为当前进程实例的别名;
group:⼤多数情况下⽤不到;
Process类常⽤⽅法:
is_alive(): 判断进程实例是否还在执⾏;
join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
start(): 启动进程实例(创建⼦进程);
run(): 如果没有给定target参数,对这个对象调⽤start()⽅法时,
就将执 ⾏对象中的run()⽅法;
terminate(): 不管任务是否完成,⽴即终⽌;
from multiprocessing import Process
import time
def task1():
print("正在听音乐")
time.sleep(1)
def task2():
print("正在编程......")
time.sleep(0.5)
def no_multi():
task1()
task2()
def use_multi():
p1 = Process(target=task1)
p2 = Process(target=task2)
p1.start()
p2.start()
p1.join()
p2.join()
#p.join() 阻塞当前进程, 当p1.start()之后, p1就提示主进程, 需要等待p1进程执行结束才能向下执行, 那么主进程就乖乖等着, 自然不会执行p2.start()
#[process.join() for process in processes]
if __name__ == '__main__':
# 主进程
start_time= time.time()
#no_multi()
use_multi()
end_time = time.time()
print(end_time-start_time
进程池
为什么需要进程池Pool? • 当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程, 十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时 可以发挥进程池的功效。 • Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满, 那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值, 那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
03多进程判断素数与进程池:
def is_prime(num):
"""判断素数"""
if num == 1:
return False
for i in range(2, num):
if num % i == 0:
return False
else:
return True
def task(num):
if is_prime(num):
print("%d是素数" % (num))
from multiprocessing import Process
#判断1000-1200之间所有的素数
def use_mutli():
ps = []
#不要开启太多进程, 创建子进程会耗费时间和空间(内存);
for num in range(1, 10000):
# 实例化子进程对象
p = Process(target=task, args=(num,))
#开启子进程
p.start()
#存储所有的子进程对象
ps.append(p)
#阻塞子进程, 等待所有的子进程执行结束, 再执行主进程;
[p.join() for p in ps]
#判断1000-1200之间所有的素数
def no_mutli():
for num in range(1, 100000):
task(num)
def use_pool():
"""使用进程池"""
from multiprocessing import Pool
from multiprocessing import cpu_count # 4个
p = Pool(cpu_count())
p.map(task, list(range(1, 100000)))
p.close() # 关闭进程池
p.join() # 阻塞, 等待所有的子进程执行结束, 再执行主进程;
if __name__ == '__main__':
import time
start_time = time.time()
#数据量大小 # 1000-1200 1-10000 # 1-100000
#no_mutli() # 0.0077722072601 # 1.7887046337127686 # 90.75180315971375
use_mutli() # 1.806459665298462
use_pool() # 0.15455389022827148 # 1.2682361602783203 # 35.63375639915466
end_time = time.time()
print(end_time - start_time)
进程间通信 How? 可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue 本身是 一个消息列队程序。 Queue.qsize(): 返回当前队列包含的消息数量; Queue.empty(): 如果队列为空,返回True,反之False ; Queue.full(): 如果队列满了,返回True,反之False; Queue.get([block[, timeout]]): 获取队列中的一条消息,然后将其从列队中移除,block默认值为True; Queue.get_nowait(): 相当Queue.get(False); Queue.put(item,[block[, timeout]]): 将item消息写入队列,block默认值 为True; Queue.put_nowait(item): 相当Queue.put(item, False) 多线程编程: 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在 进程之中,是进程中的实际运作单位。 每个进程至少有一个线程,即进程本身。进程可以启动多个线程。操作系统像并 行“进程”一样执行这些线程 线程和进程各自有什么区别和优劣呢? • 进程是资源分配的最小单位,线程是程序执行的最小单位。 • 进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间. • 进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程 共享全局变量、静态变量等数据,难点:处理好同步与互斥。 线程分类 有两种不同的线程: • 内核线程 • 用户空间线程或用户线程 内核线程是操作系统的一部分,而内核中没有实现用户空间线程。 方法一分析 • 多线程程序的执行顺序是不确定的。 • 当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪 (Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。 • 代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、 run函数中 每次循环的执行顺序都不能确定。
04 多进程编程方法2:
"""
创建子类, 继承的方式
"""
from multiprocessing import Process
import time
class MyProcess(Process):
"""
创建自己的进程, 父类是Process
"""
def __init__(self, music_name):
super(MyProcess, self).__init__()
self.music_name = music_name
def run(self):
"""重写run方法, 内容是你要执行的任务"""
print("听音乐%s" %(self.music_name))
time.sleep(1)
#开启进程: p.start() ====== p.run()
if __name__ == '__main__':
for i in range(10):
p = MyProcess("音乐%d" %(i))
p.start()
05多线程实现的方法1
"""
通过实例化对象的方式实现多线程
"""
import time
import threading
def task():
"""当前要执行的任务"""
print("听音乐........")
time.sleep(1)
if __name__ == '__main__':
start_time = time.time()
threads = []
for count in range(5):
t = threading.Thread(target=task)
#让线程开始执行任务
t.start()
threads.append(t)
#等待所有的子线程执行结束, 再执行主线程;
[thread.join() for thread in threads]
end_time = time.time()
print(end_time-start_time)
项目案例: 基于多线程的批量主机存活探测 项目描述: 如果要在本地网络中确定哪些地址处于活动状态或哪些计算机处于活动状态, 则可以使用此脚本。我们将依次ping地址, 每次都要等几秒钟才能返回值。这可以在Python 中编程,在IP地址的地址范围内有一个for循环和一个os.popen(“ping -q -c2”+ ip)。 项目瓶颈: 没有线程的解决方案效率非常低,因为脚本必须等待每次ping。 优点: 在一个进程内的所有线程共享全局变量,能够在不使用其他方式的前提 下完成多线 程之间的数据共享(这点要比多进程要好) 缺点: 线程是对全局变量随意遂改可能造成多线程之间对全局变量 的混乱(即线程非安全)
07 ip地址归属地批量查询任务:
import requests
import json
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from threading import Thread
def task(ip):
"""获取指定IP的所在城市和国家并存储到数据库中"""
#获取网址的返回内容
url = 'http://ip-api.com/json/%s' % (ip)
try:
response = requests.get(url)
except Exception as e:
print("网页获取错误:", e)
else:
# 默认返回的是字符串
"""
{"as":"AS174 Cogent Communications","city":"Beijing","country":"China","countryCode":"CN","isp":"China Unicom Shandong Province network","lat":39.9042,"lon":116.407,"org":"NanJing XinFeng Information Technologies, Inc.","query":"114.114.114.114","region":"BJ","regionName":"Beijing","status":"success","timezone":"Asia/Shanghai","zip":""}
"""
contentPage = response.text
#将页面的json字符串转换成便于处理的字典;
data_dict = json.loads(contentPage)
#获取对应的城市和国家
city = data_dict.get('city', 'null') # None
country = data_dict.get('country', 'null')
print(ip, city, country)
#存储到数据库表中ips
ipObj = IP(ip=ip, city=city, country=country)
session.add(ipObj)
session.commit()
if __name__ == '__main__':
engine = create_engine("mysql+pymysql://root:westos@172.25.254.123/pymysql",
encoding='utf8',
# echo=True
)
#创建缓存对象
Session = sessionmaker(bind=engine)
session = Session()
#声明基类
Base = declarative_base()
class IP(Base):
__tablename__ = 'ips'
id = Column(Integer, primary_key=True, autoincrement=True)
ip = Column(String(20), nullable=False)
city = Column(String(30))
country = Column(String(30))
def __repr__(self):
return self.ip
#创建数据表
Base.metadata.create_all(engine)
#1.1.1.1 -- 1.1.1.10
threads = []
for item in range(10):
ip = '1.1.1.' + str(item + 1) # 1.1.1.1 -1.1.1.10
#task(ip)
#多线程执行任务
thread = Thread(target=task, args=(ip,))
#启动线程并执行任务
thread.start()
#存储创建的所有线程对象;
threads.append(thread)
[thread.join() for thread in threads]
print("任务执行结束.........")
print(session.query(IP).all())
07 多线程实现方法二:
"""
创建子类
"""
from threading import Thread
class GetHostAliveThread(Thread):
"""
创建子线程, 执行的任务:判断指定的IP是否存活
"""
def __init__(self, ip):
super(GetHostAliveThread, self).__init__()
self.ip = ip
def run(self):
# # 重写run方法: 判断指定的IP是否存活
#"""
#>>> # os.system() 返回值如果为0, 代表命令正确执行,没有报错; 如果不为0, 执行报错;
#...
#>>> os.system('ping -c1 -w1 172.25.254.49 &> /dev/null')
#0
#>>> os.system('ping -c1 -w1 172.25.254.1 &> /dev/null')
#256
#"""
import os
#需要执行的shell命令
cmd = 'ping -c1 -w1 %s &> /dev/null' %(self.ip)
result = os.system(cmd)
#返回值如果为0, 代表命令正确执行,没有报错; 如果不为0, 执行报错;
if result != 0:
print("%s主机没有ping通" %(self.ip))
if __name__ == '__main__':
print("打印172.25.254.0网段没有使用的IP地址".center(50, '*'))
for i in range(1, 255):
ip = '172.25.254.' + str(i)
thread = GetHostAliveThread(ip)
thread.start()
共享全局变量:如何解决线程不安全问题? GIL(global interpreter lock): python解释器中任意时刻都只有一个线程在执行; Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控 制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程 在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的 访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个 线程在运行。
08 共享数据:
money = 0
def add():
for i in range(1000000):
global money
lock.acquire()
money += 1
lock.release()
def reduce():
for i in range(1000000):
global money
lock.acquire()
money -= 1
lock.release()
if __name__ == '__main__':
from threading import Thread, Lock
# 创建线程锁
lock = Lock()
t1 = Thread(target=add)
t2 = Thread(target=reduce)
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
线程同步 线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内 存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作. 同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。 "同"字从字面上容易理解为一起动作 其实不是, "同"字应是指协同、协助、互相配合。 死锁 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时 等待对方的资 源,就会造成死锁。
09 死锁问题:
"""
在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时 等待对⽅的资源,就会造成死锁。
"""
import time
import threading
class Account(object):
def __init__(self, id, money, lock):
self.id = id
self.money = money
self.lock = lock
def reduce(self, money):
self.money -= money
def add(self, money):
self.money += money
def transfer(_from, to, money):
if _from.lock.acquire():
_from.reduce(money)
time.sleep(1)
if to.lock.acquire():
to.add(money)
to.lock.release()
_from.lock.release()
if __name__ == '__main__':
a = Account('a', 1000, threading.Lock()) # 900
b = Account('b', 1000, threading.Lock()) # 1100
t1 = threading.Thread(target=transfer, args=(a, b, 200))
t2 = threading.Thread(target=transfer, args=(b, a, 100))
t1.start()
t2.start()
print(a.money)
print(b.money)
协程,又称微线程,纤程。英文名Coroutine。协程看上去也是子程序,但执行过程中, 在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。 协程优势 • 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制, • 没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优 势越明显。 • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控 制共享资源时也不需要加锁,因此执行效率高很多 gevent实现协程 基本思想: 当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到 IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程 序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而 不是等待IO。
10 gevent实现协程
import gevent
import requests
import json
from gevent import monkey
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from threading import Thread
from gevent import monkey
#打补丁
monkey.patch_all()
def task(ip):
"""获取指定IP的所在城市和国家并存储到数据库中"""
#获取网址的返回内容
url = 'http://ip-api.com/json/%s' % (ip)
try:
response = requests.get(url)
except Exception as e:
print("网页获取错误:", e)
else:
# 默认返回的是字符串
"""
{"as":"AS174 Cogent Communications","city":"Beijing","country":"China","countryCode":"CN","isp":"China Unicom Shandong Province network","lat":39.9042,"lon":116.407,"org":"NanJing XinFeng Information Technologies, Inc.","query":"114.114.114.114","region":"BJ","regionName":"Beijing","status":"success","timezone":"Asia/Shanghai","zip":""}
"""
contentPage = response.text
#将页面的json字符串转换成便于处理的字典;
data_dict = json.loads(contentPage)
#获取对应的城市和国家
city = data_dict.get('city', 'null') # None
country = data_dict.get('country', 'null')
print(ip, city, country)
#存储到数据库表中ips
ipObj = IP(ip=ip, city=city, country=country)
session.add(ipObj)
session.commit()
if __name__ == '__main__':
engine = create_engine("mysql+pymysql://root:westos@172.25.254.123/pymysql",
encoding='utf8',
# echo=True
)
#创建缓存对象
Session = sessionmaker(bind=engine)
session = Session()
#声明基类
Base = declarative_base()
class IP(Base):
__tablename__ = 'ips'
id = Column(Integer, primary_key=True, autoincrement=True)
ip = Column(String(20), nullable=False)
city = Column(String(30))
country = Column(String(30))
def __repr__(self):
return self.ip
#创建数据表
Base.metadata.create_all(engine)
#使用协程
gevents = [gevent.spawn(task, '1.1.1.' + str(ip + 1)) for ip in range(10)]
gevent.joinall(gevents)
print("执行结束....")
总结