Python进程,线程,协程 编程实践

进程

资源分配的基本单位,分配 内存,CPU等。
代码 + 用到的资源
并行 真的同时进行;并发 在一段时间里都运行,不是同时的。

会牵扯到 文件描述符,软连接 这些底层的系统知识。
Linux中 一切皆文件,用文件描述符标识每个文件对象,每个进程最多可以有9个文件描述符,前三个是保留的,用于标准输入输出,标准错误输出。
例如:对于一个套接字,实际上就是 一个文件的 文件描述符(012之外的),指向这个文件,
当使用多进程时,会复制一份程序和资源,用来处理这个套接字,那么就有了两个 链接 连接到这个文件,所以要分别调用 close() 才算是把这个套接字关闭。
而对于,多线程,则不会复制这个套接字。

多进程:

	import multiprocessing

	p = multiprocessing.Process(target = 函数名, args = (参数列表,))
	p.start()

会耗费较多的资源。写时拷贝,就是共用代码,当不同的地方时,拷贝一份代码。
在子进程里产生的异常信息,不会显示

进程间通信:
对应单台计算机中的进程间通信的小样式:通过队列的方式来发送和接受消息。

	def 函数名1(q):
		q.put(数据)
	def 函数名2(q):
		q.get()
    #创建一个队列,用于存放进程的数据
	q = multiprocessing.Queue()
	
	p1 = multiprocessing.Process(target = 函数名1, args = (q,))
	p2 = multiprocessing.Process(target = 函数名2, args = (q,))
	p1.start()
	p2.start()

进程池:
当要创建的进程很多时,手动用上述方式创建较慢。可以用multiprocessing模块的Pool方法。
可以指定最大进程数,当没有达到时,有新请求就创建一个新的,当达到最大进程数时,就需要等待有进程结束,才会用之前的进程执行新任务。重复利用进程,进而消减了进程的创建和销毁消耗。

pool = multiprocessing.Pool(n)  # 最多创建n个进程。
pool.apply_async(函数名,(参数列表,))    #可以调用 多余n个(可以用for循环来循环创建),来创建多个进程,
										 # 会利用进程池中的进程来完成任务

pool.close()    # 关闭进程池,不会再接受新的请求
pool.join()    #  等待pool 中的所有进程接受,放在close之后。就是避免主进程的代码执行完后的主进程结束。
线程

线程依存于进程中,开销小,但不利于资源的管理和保护 ,没有独立的内存资源。操作系统调度时间片的基本单位。

多线程:

	import threading
	t = threading.Thread(target = 函数名, args = (参数列表,))
	t.start()
	每调用一次,就会创建一个线程来执行这个函数。 threading.Thread 只是创建了一个子线程对象,只有这个对象调用start时才实际创建出子线程来执行函数代码。

查看当前有哪些线程在运行:

threading.enumerate()
会返回 描述线程的列表

当 线程运行的函数结束时,这个子线程也结束
如果主线程提前结束,那么运行的所有子线程也结束。

通过继承 Thread类来完成创建线程。

class MyThread(threading.Thread):
	def run(self):
		语句。。。

if __name__ = "__main__":
	t = MyThread()
	t.start()

当继承Thread类后,必须定义 run()方法,方这个类调用strat()时会自动调用run()方法。也就是说,可以在这个类中定义别的其他方法,然后放入run()方法中来调用,这样就可以运行多个函数啦。

多线程共享全局变量
在一个函数中,对全局变量进行修改时,是否需要使用 global 进行声明,要看是否对全局变量的指向进行了修改。
如果是 全局变量 等于 一个新的值,那么就需要加global.
也可以把全局变量当做参数传入函数,也可以达到共享的效果。
共享数据可以为处理带来很大的方便,比如一个线程抓取数据,一个线程清洗数据,一个线程发送清洗好的数据。

共享全局变量可能带来的问题:
资源竞争问题。当一个线程正在处理某个全局变量时,另一个也去处理,(本该在前一个处理完的基础上再取值进行处理的),这样前一个处理完后会覆盖后一个处理的结果,这样就乱套了。

同步,协同步调,按预定的先后顺序执行。
当某个线程处理共享变量时 ,将其锁定,是其他线程不能处理,等该线程处理完,释放资源后, 其他线程才可以再锁定该资源来使用。

#创建锁
mutex = threading.Lock(0
#上锁,如果之间锁没被上,就上锁成功,如果之前锁在别处被上,此时	阻塞在这里等待锁被解开
muxtex.acquire()
执行处理全局变量的代码,被上下两个包围的代码越少越好,核心的共享资源处用锁就好
# 解锁
mutex.release()
协程

迭代器:
迭代是访问 集合元素的一种方式,迭代器是一个可以记住遍历到的位置的对象,迭代器只能从集合的第一个元素开始直到所有元素被访问完,迭代器只能往前不会后退。

迭代器中是 直接生成全部数据存放起来。
例如:range(10) ,在Python2中会立马返回一个列表,xrange(10) 什么时候调用什么时候返回。

除了用于 for , 对于 list,tuple 也可以接受迭代器,来把数据转换为相应的形式,也是得益于迭代器的特性。

一个创建迭代器的示例:

# coding=utf-8

# from collections import Iterable, Iterator

class MyIterableClass:
    def __init__(self):
        self.names = list()

def add(self, name):
    self.names.append(name)

# 如果想要一个对象成为一个可迭代的对象,需要 实现__iter__方法,其需要返回一个迭代器
def __iter__(self):
    return MyIterator(self)

class MyIterator:  # 一个类中只要包含者两个方法iter和next,就是一个 迭代器

def __init__(self, obj):
    self.obj = obj
    self.current_num = 0

def __iter__(self):
    pass

def __next__(self):

    if self.current_num < len(self.obj.names):
        ret = self.obj.names[self.current_num]
        self.current_num += 1
        return ret
    else:
        raise StopIteration


# 上述两个和为一
class MyIterableClassTwo:
    def __init__(self):
        self.names = list()
        self.current_num = 0

def add(self, name):
    self.names.append(name)

# 如果想要一个对象成为一个可迭代的对象,需要 实现__iter__方法,其需要返回一个迭代器
def __iter__(self):
    return self

def __next__(self):

    if self.current_num < len(self.names):
        ret = self.names[self.current_num]
        self.current_num += 1
        return ret
    else:
        raise StopIteration


if __name__ == '__main__':
    name_iter = MyIterableClassTwo()
    name_iter.add("wl")
    name_iter.add("96")
    name_iter.add("jiang")

for name in name_iter:
    print(name)

# 判断name_iter是否为可迭代对象
# print(isinstance(name_iter, Iterable))
# 判断 iter(name_iter) ,是否返回一个 迭代器
# print(isinstance(iter(name_iter), Iterator))

生成器:
是一种特殊的迭代器,只要 函数里 有 yield 关键字,那么这个函数就变成了生成器。调用时会创建并返回一个生成器对象。

在执行到 yield 行时,会返回 后面的值,并阻塞在这里,等下次再取值时,会重新从这一行后面开始执行。也就是每次next()时,才继续进行后面的数据生成。

# 生成斐波那契数列
def create_num(nums):
	a,b = 0,1
	current_num = 0
	while current_num < nums:
		yield a      #  卡在这里,被再次next时,继续执行后面的  ,还可以    ret = yield a   ,在生成器调用send()时,会把发来的数据,给到ret,然后生成器继续之前的运行。
		a,b = b, a+b
		current_num += 1
	return “OK....”            //当生成器把数据生成完时,在next()的捕获异常处,会收到返回值
fact = create_num(10)
num = next(fact)
num2 = fact.send(123或None)

生成器可以这样创建: (x*3 for x in range(10)) 返回的是一个生成器,调用时才会来生成需要的数据,如果把()换为 [ ] 会直接生成一个列表。

用生成器实现多任务: 并发的形式

	import time

# 生成器 并发的多任务
def task1():
    while True:
        print("------1------")
        time.sleep(1)
        yield
def task2():
    while True:
        print("-------2-----")
        time.sleep(1)
        yield

def main():
    t1 = task1()
    t2 = task2()
    while True:
        next(t1)
        next(t2)
 if __name__ == '__main__':
 main()

greenlet : 对yield 进行了封装,可以更简便的达到上述效果:

from greenlet import greenlet 

import time

def task1():
    while True:
        print("------1------")
        gr2.switch()
        time.sleep(1)
    
def task2():
    while True:
        print("-------2-----")
        gr1.switch()
        time.sleep(1)
        

if __name__ == '__main__':
    gr1 = greenlet(task1)
    gr2 = greenlet(task2)
    
    gr1.switch()

greenlet 已经实现 了协程,但是还需要 代码说明切换。
gevent进行了更进一步的封装:,能够在一个 greenlet 遇到 IO操作时,自动切换到其他 greenlet 执行,当IO完成,再切换回来执行。
需要把延时或耗时的操作,换为gevent里提供的,例如 gevent.socket(),gevent.sleep()

如果之前的好多代码没有使用 gevent里提供的耗时模块,
可以 使用 from gevent import monkey monkey.patch_all(), nam , 会自动把 time.sleep()换为 gevent.sleep()

import gevent
def f(n):
	for i in range(n):
		print(gevent.getcurrent(), i)
		gevent.sleep(0.5)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,4)
g3 = gevent.spawn(f,3)
g1.join()
g2.join()
g3.join()
# 上述六行代码还可以写为:
gevent.joinall([
	gevent.spawn(f,5),
	gevent.spawn(f,4),
	gevent.spawn(f,3)
])

协程依赖于线程,也就是 在一个线程中,遇到 耗时的任务时(如网路下载等待时),转而执行同一个线程里的其他任务。

利用单线程,处理多个请求,而不阻塞

在单进程单线程里,把套接字设置为 非阻塞,可以 实现 服务器处理多个请求。
设置非阻塞的方式: 套接字.setblocking(false)

def main():
"""整体控制"""

#     1.创建套接字,返回一个 特定类型的套接字对象,还有待于确定具体的值。
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#     2.绑定本机IP和端口,用于 客户端找到 特定的进程
tcp_server_socket.bind(("", 7891))
#     3.变为 监听套接字,等待 客户端发送请求 来激活阻塞,可以指定能够接受的最大连接数
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False)
client_socket_list = list()
while True:

    # 实现 可以处理多个请求,用单进程单线程的方式
    try:
        new_socket, client_addr = tcp_server_socket.accept()
    except Exception as ret:
        print("---没有新客户到来,继续监听")
    else:
        print("如果没有上个异常,就是来了新的客户端的访问")
        new_socket.setblocking(False)     # 设置套接字为非阻塞的方式
        client_socket_list.append(new_socket)

    for client_socket in client_socket_list:
        try:
            recv_data = client_socket.recv(1024)
        except Exception as ret:
            print("客户的数据还没有发送过来")
        else:
            if recv_data:
                print("客户端发来了数据")
                # 可以调用service_high 来出来请求。函数在后一段代码中
            else:
                # 客户端调用close,发来了空的数据
                client_socket_list.remove(client_socket)
                client_socket.close()

#     关闭监听套接字
tcp_server_socket.close()

上面这个 虽然是 HTTP/1.1 但是 由于 手动调用了 close() ,所以效果像 短连接。

建立长连接的代码:
改动 发送回到 header ,添加 Content-Length:body的长度,
这样,浏览器就知道,此次的数据发送完啦。

改为长连接形式后的代码:

def main():
"""整体控制"""

#     1.创建套接字,返回一个 特定类型的套接字对象,还有待于确定具体的值。
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#     2.绑定本机IP和端口,用于 客户端找到 特定的进程
tcp_server_socket.bind(("", 7891))
#     3.变为 监听套接字,等待 客户端发送请求 来激活阻塞,可以指定能够接受的最大连接数
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False)
client_socket_list = list()
while True:
    time.sleep(2)
    #     4.等待客户端的连接,若有客户端发来请求,会返回一个客户端的套接字用于 数据的回传
    # new_socket, client_addr = tcp_server_socket.accept()

    # 5.为这个客户端的请求做处理
    # service_client(new_socket)
    # service_high(new_socket)

    # 实现 可以处理多个请求,用单进程单线程的方式
    try:
        new_socket, client_addr = tcp_server_socket.accept()
    except Exception as ret:
        print("---没有新客户到来,继续监听")
    else:
        print("如果没有上个异常,就是来了新的客户端的访问")
        new_socket.setblocking(False)     # 设置套接字为非阻塞的方式
        client_socket_list.append(new_socket)

    for client_socket in client_socket_list:
        try:
            recv_data = client_socket.recv(1024)
        except Exception as ret:
            print("客户的数据还没有发送过来")
        else:
            if recv_data:
                print("客户端发来了数据")
            #     进行数据的处理。。。可以调用service_client
                service_high(client_socket,recv_data)
            else:
                # 客户端调用close,发来了空的数据
                client_socket_list.remove(client_socket)
                client_socket.close()
#     关闭监听套接字
tcp_server_socket.close()


# 修改为长连接的形式
def service_high(new_socket,request):
    # 1. 接受浏览器发来的请求,并解析
    # request = new_socket.recv(1024).decode("utf-8")
    request_lines = request.decode("utf-8").splitlines()
    print("=" * 30)
    print(request_lines)

ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])

if ret:
    file_name = ret.group(1)
    if file_name == "/":
        file_name = "/index.html"

# 返回 HTTP 格式的数据给 浏览器
try:
    f = open("../frontEnd" + file_name, 'rb')
except:
    response = "HTTP/1.1 404 NOT FOUND\r\n"
    response += "Content-Length:%d\r\n" % len("---- file not find----")
    response += "\r\n"
    response += "---- file not find----"
    new_socket.send(response.encode("utf-8"))
else:
    response_body = f.read()
    f.close()

    response_header = "HTTP/1.1 200 OK\r\n"
    response_header += "Content-Length:%d\r\n" % len(response_body)
    response_header += "\r\n"

    response = response_header.encode("utf-8") + response_body
    new_socket.send(response)

# new_socket.close()    把这行去除了,这样就不是短连接的方式了

上面这个代码的运行效率是不高的,因为当 访问的客户端很多事,需要遍历的套接字也很多,每次遍历都要把用户态的套接字,复制到操作系统内核中,等待检测是否有文件到来,这样遍历一遍就很耗时。是轮询的方式处理套接字。

epoll

web静态服务器epoll 提供了 一个 应用程序和操作系统内核 共用的内存(内存映射技术mmp),避免了复制的耗时,并且以 事件通知的形式来处理每个套接字。

epoll在Python中的简单应用:

import select
	# 创建一个epoll对象
	epl = select.epoll()

	# 向epoll中注册 套接字, 第一个参数 文件描述符,第二个参数 	什么事件类型,是接受还是发送还是错误输出
	epl.register(套接字对象.fileno(), select.EPOLLIN)
	# 默认会阻塞,当数据到达套接字的对应的文件时,操作系统检测到,以事件通知的方式,告诉程序,并解阻塞
	# 会返回元祖列表,(文件描述符,事件类型)
	fd_event_list = epl.poll()
	for fd,event in fd_event_list :
		根据这些找到相应的套接字对象,再进行相应的处理。可以利用字典来预先标记对应的fd和套接字。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值