python进程线程协程区别_Python中的多任务(线程、进程、协程)

1. 多任务的概念

多任务简单地说,就是操作系统可以同时运行多个任务。分为并行和并发两种。

1.1 并行

指的是任务数小于等于CPU核数,即任务真的是一起执行的

1.2 并发

指的是任务数多于CPU核数,通过操作系统的各种任务调度算法,实现用多个任务一起执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

1.3 网络的概念

网络就是辅助双方或多方,连接在一起的辅助工具

1.4 网络的作用

网络能够实现数据的相互传送,能够通信,互相传递信息等。

1.5 ip地址

ip地址就是在网络中标记一台计算机的地址,比如192.168.1.1;在本地局域网上是唯一的。

在这么多网络IP中,国际规定有一部分IP地址是用于我们的局域网使用,也就

是属于私网IP,不在公网中使用的,它们的范围是:

10.0.0.0~10.255.255.255

172.16.0.0~172.31.255.255

192.168.0.0~192.168.255.255

复制代码

注意

IP地址127.0.0.1~127.255.255.255用于回路测试,

如:127.0.0.1可以代表本机IP地址,用http://127.0.0.1就可以测试本机中配置的Web服务器。

1.6 端口(port)

端口号就是电脑上某个程序对应的地址,用来标记某个程序,端口号只有整数,范围是从0到65535

知名端口:知名端口是众所周知的端口号,范围从0到1023,都已被占用

动态端口:动态端口的范围是从1024到65535,之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。

1.5 socket

socket是一种进程之间的通信方式,简称套接字。它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的

例如我们每天浏览网页、QQ 聊天、收发 email 等等。

创建socket

import socket

socket.socket(AddressFamily, Type)

复制代码

说明:

函数 socket.socket 创建一个 socket,该函数带有两个参数:

Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET

Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)

2. 线程

python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用

2.1 多线程执行

#coding=utf-8

import threading

import time

def saySorry():

print("我能吃饭了吗?")

time.sleep(1)

if __name__ == "__main__":

for i in range(5):

t = threading.Thread(target=saySorry)

t.start() #启动线程,即让线程开始执行

复制代码使用的是多线程并发的操作,当调用start()时,才会真正的创建线程,并且开始执行

主线程结束后,子进程立即结束

2.2 线程执行代码的封装

python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。

2.3 线程的执行顺序

多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞,到sleep结束后,线程进入就绪状态,等待调度。而线程调度将自行选择一个线程执行。

2.4 多线程共享全局变量

在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据

缺点就是,线程是对全局变量随意更改可能造成多线程之间对全局变量的混乱

如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确

2.5 互斥锁

当多个线程几乎同时修改某个共享数据的时候,需要进行同步控制。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

threading模块中定义了Lock类,可以方便的处理锁定:

# 创建锁

mutex = threading.Lock()

# 锁定

mutex.acquire()

# 释放

mutex.release()

复制代码如果这个锁之前没有上锁的,那么acquire不会堵塞

如果在调用acquire对这个锁上锁之前它已经被其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

2.6 死锁

在线程键共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

可以添加超时时间等,解决死锁

案例:多线程版udp聊天器

import socket

import threading

def send_msg(udp_socket):

"""获取键盘数据,并将其发送给对方"""

while True:

# 1. 从键盘输入数据

msg = input("\n请输入要发送的数据:")

# 2. 输入对方的ip地址

dest_ip = input("\n请输入对方的ip地址:")

# 3. 输入对方的port

dest_port = int(input("\n请输入对方的port:"))

# 4. 发送数据

udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))

def recv_msg(udp_socket):

"""接收数据并显示"""

while True:

# 1. 接收数据

recv_msg = udp_socket.recvfrom(1024)

# 2. 解码

recv_ip = recv_msg[1]

recv_msg = recv_msg[0].decode("utf-8")

# 3. 显示接收到的数据

print(">>>%s:%s" % (str(recv_ip), recv_msg))

def main():

# 1. 创建套接字

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. 绑定本地信息

udp_socket.bind(("", 7890))

# 3. 创建一个子线程用来接收数据

t = threading.Thread(target=recv_msg, args=(udp_socket,))

t.start()

# 4. 让主线程用来检测键盘数据并且发送

send_msg(udp_socket)

if __name__ == "__main__":

main()

复制代码

3. 进程

multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动

# -*- coding:utf-8 -*-

from multiprocessing import Process

import os

import time

def run_proc():

"""子进程要执行的代码"""

print('子进程运行中,pid=%d...' % os.getpid()) # os.getpid获取当前进程的进程号

print('子进程将要结束...')

if __name__ == '__main__':

print('父进程pid: %d' % os.getpid()) # os.getpid获取当前进程的进程号

p = Process(target=run_proc)

p.start()

复制代码

3.1 process语法结构如下:

Process([group [, target [, name [, args [, kwargs]]]]])

target:如果传递了函数的引用,可以任务这个子进程就执行这里的代码

args:给target指定的函数传递的参数,以元组的方式传递

kwargs:给target指定的函数传递命名参数

name:给进程设定一个名字,可以不设定

group:指定进程组,大多数情况下用不到

Process创建的实例对象的常用方法:

start():启动子进程实例(创建子进程)

is_alive():判断进程子进程是否还在活着

join([timeout]):是否等待子进程执行结束,或等待多少秒

terminate():不管任务是否完成,立即终止子进程

Process创建的实例对象的常用属性:

name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

pid:当前进程的pid(进程号)

进程之间不共享全局变量

3.2 进程与线程的区别

进程是系统进行资源分配和调度的一个独立单位

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其它线程共享进程所拥有的全部资源

一个程序至少有一个进程,一个进程至少有一个线程

线程的划分尺度小于进程,使得多线程序的并发性高。

进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率

1

线程不能独立执行,必须依存在进程中

可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人

1

线程和进程在使用上各有优缺点:线程执行开销小。但不利于资源的管理和保护,而进程正相反。

3. 进程池Pool

当需要创建的子进程数量巨大时,就可以用到multiprocessing模块提供的Pool方法

apply_async(func[,args[,kwds]]):使用非阻塞方式调用func,(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表

close():关闭Pool,使其不再接收新的任务

terminate():不管任务是否完成,立即终止

join():主进程阻塞,等待子进程的退出,必须在close或terminate之后使用

在程序执行过程中,关闭进程池,则程序会立即停止,不会再继续执行后续语句。

4. Queue

进程是通过导入Queue,用Queue实现多进程之间的数据传递,Queue本身是一个消息队列程序。

Queue.qsize():返回当前队列包含的消息数量

Queue.empty():如果队列为空,返回True,反之False

Queue.full():如果队列满了,返回True,反之False

Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为True

Queue.put_nowait(item):相当Queue.put(item,False)

5. 迭代器

5.1 可迭代对象

我们把可以通过for...in...这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)**

可迭代对象的本质就是可以向我们提供一个这样的中间"人"即迭代器帮助我们对其进行迭代遍历使用。

可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据,也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象。

5.2 如何判断一个对象是否可以迭代

可以使用isinstance()判断一个对象是否是iterable对象

生成器是一类特殊的迭代器

5.3 创建生成器的方法

只要把一个列表生成式的[]改成()

只要在def中有yield关键字的就称为生成器,使用了yield关键字的函数不再是函数,而是生成器

5.4 唤醒

除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒来执行,使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据

6. gevent

6.1 geven使用

import gevent

def f(n):

for i in range(n):

print(gevent.getcurrent(),i)

g1 = gevent.spawn(f, 5)

g2 = gevent.spawn(f, 5)

g3 = gevent.spawn(f, 5)

g1.join()

g2.join()

g3.join()

复制代码

运行结果

0

1

2

3

4

0

1

2

3

4

0

1

2

3

4

复制代码

3个greenlet是依次运行而不是交替运行,可以导入time,添加延时切换执行

6.2 线程、进程、协程对比

进程是资源分配的单位

线程是操作系统调度的单位

进程切换需要的资源很大,效率很低

线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)

协程切换任务资源很小,效率高

多进程、多线程根据CPU核数不一样可能是并行的,但是协程是在一个线程中所以是并发

7. 阻塞和非阻塞

Python中遇到recv、accept、等语句时,是默认阻塞的,即如果不设置一些条件时,程序会一直等待下去。

而非阻塞就是通过设置条件,把阻塞变为非阻塞,所以转变语法需要在阻塞之前设置

例:server_socket.setblocking(False)

复制代码

8. Http协议

8.1 浏览器向服务器发送http请求

请求包括:请求头、请求体、请求行

8.2 服务器向浏览器返回HTTP响应

响应包括:响应头、响应行、响应体

9 长连接和短连接

9.1 短连接

短连接就是建立连接、接收数据、关闭连接,每次只获取1次数据1

9.2 长连接

长连接就是建立连接后,多次请求,直到没有请求后关闭的连接1

代码最后有close操作的其实都是短连接,长连接不能在连接中强制调用close

10. 三次握手,四次挥手

TCP通信的整个过程1

三次握手:建立连接时,客户端向服务器发送连接请求,服务器向客户端回应请求的同时向客户端发送连接请求,客户端回应请求,服务器收到时,三次握手成功,双方连接成功。

四次挥手:客户端调用close时,向服务器发送请求,服务器回应请求同时解堵塞,调用自己close后,再次向客户端发送close请求,此时客户端方会等待两个最大报文时间,等待接收服务器的请求(等待是为了避免断网,断电等特殊情况),收到服务器的请求后,向服务器回应请求,服务器收到请求后关闭,4次挥手成功,双方关闭连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值