##1.0 进程
1.1 程序与 进程
进程:是系统进行资源分配的基本单位
进程:动态的概念,运行起来的程序,包括程序,以及运行程序所需要的计算机资源等
程序:静态的概念,我们写的.py .go等,都是程序
1.2 多进程的概念
import multiprocessing
import os
num = 0
def add_num1():
global num
for i in range(1000000):
num += 1
#释放锁
print('子进程1已经完成,此时得到的num','当前子进程编号',num,os.getpid(),'当前子进程父进程编号(主进程)',os.getppid())
def add_num2():
global num
for i in range(1000000):
num += 1
print('子进程2已经完成,此时得到的num',num)
if __name__ == '__main__':
print('主进程编号',os.getpid())
process1 = multiprocessing.Process(target=add_num1)
process2 = multiprocessing.Process(target=add_num2)
process1.start()
process2.start()
注意点:
1、os.getpid获取执行当前任务的进程编号
2、os.getppid获取的是当前进程的父进程编号
3、多进程之间不共享全局变量,多个进程之间是独立的
###1.3 传参
def add_num1(count,name):
global num
for i in range(count):
num += 1
#释放锁
print('子进程1已经完成,此时得到的num',num,'当前子进程编号',os.getpid(),'当前子进程父进程编号(主进程)',os.getppid())
process1 = multiprocessing.Process(target=add_num1,args=(1000,),kwargs={'name':'小王'})
1.4 队列
try:
for i in range(4):
que.put('123',True,1)
except Exception as e:
print('消息队列已经满了,','当前数据有%s个'%que.qsize())
注意点:
1、put在默认情况下,如果队列已经满了,一直阻塞,等待队列出现空余位置
2、如果设置了timeout,那么等待过后,会报错
3、block设置为False,直接报错
if not que.full():
for i in range(que.qsize()):
que.put_nowait('123')
from multiprocessing import Queue,Process
que = Queue(3)
# que.put('123')
# que.put('123')
# # que.put('123',block=True,timeout=10)
# # que.put('123')
# # que.put('123')
# que.get()
# try:
# for i in range(4):
# que.put('123',True,1)
# except Exception as e:
# print('消息队列已经满了,','当前数据有%s个'%que.qsize())
# if not que.full():
# for i in range(que.qsize()):
# que.put_nowait('123')
def readData(que):
if not que.empty():
data = que.get_nowait()
print(data)
def writeData(que):
if not que.full():
try:
que.put_nowait('123')
except :
print('出错')
if __name__ == '__main__':
q = Queue()
p1 = Process(target=writeData,args=(q,))
p2 = Process(target=readData,args=(q,))
p1.start()
import time
time.sleep(10)
p2.start()
from multiprocessing import Pool
import os
def info():
print('执行该函数的进程编号',os.getpid())
import time
time.sleep(1)
print('这是一个函数')
if __name__ == '__main__':
po = Pool(3)
print(po)
for i in range(20):
po.apply_async(info)
print('############开始#############')
#将进程池关闭,不再接收新的请求
po.close()
#等待进程池中的进程全部结束
po.join()
print('###############结束########3')
1.5 进程与线程的区别
1、线程是进程中的一个实例,真正干活的人是线程,
2、进程只是资源分配的基本单位,线程是调度的基本单位
3、没有进程就没有线程这个概念
4、不同的进程一起运行,其中一个进程死了,其他进程不受影响
5、在同一个进程内,如果线程消亡,那这个进程受影响,所以从健壮性来讲,进程比线程更加稳定
6、线程共享全局变量,进程创建子进程相当于粘贴复制,此时相当于创建了一份副本,不共享全局变量
2、线程
2.1 定义
默认情况下,程序启动只有一个线程,这个线程就是主线程,线程是CPU调度的基本单位
2.2 统计线程的个数
import threading
print('hello')
print('#######统计当前线程运行的个数######')
print(threading.active_count())
展示:
hello
#######统计当前线程运行的个数######
1
注意点:
在没有添加进程的情况下,我们依旧可以找到一个线程,这个线程是主线程
2.3 创建多线程
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
import threading
import time
# print('hello')
# print('#######统计当前线程运行的个数######')
# print(threading.active_count())
def calm():
for i in range(10):
time.sleep(0.1)
print('第',i,'次冷静一下')
def smoke():
for j in range(10):
time.sleep(0.1)
print('第',j,'支烟')
if __name__ == '__main__':
# smoke()
# calm()
print('当前进程的线程个数',threading.active_count())
#创建多线程
thread_smoke = threading.Thread(target=smoke)
print('当前进程的线程个数', threading.active_count())
thread_calm = threading.Thread(target=calm)
#调用
thread_smoke.start()
thread_calm.start()
print('当前进程的线程个数', threading.active_count())
展示:
当前进程的线程个数 1
当前进程的线程个数 1
当前进程的线程个数 3
第 0 支烟
第 0 次冷静一下
第 1 支烟
第 1 次冷静一下
第 2 支烟
第 2 次冷静一下
第 3 次冷静一下
第 3 支烟
第 4 支烟
第 4 次冷静一下
第 5 次冷静一下
第 5 支烟
第 6 次冷静一下
第 6 支烟
第 7 支烟
第 7 次冷静一下
第 8 支烟
第 8 次冷静一下
第 9 次冷静一下
第 9 支烟
2.4 多线程的注意点
1、线程的执行是无序的,因为是CPU进行调度的
2、守护主线程,主线程退出,那子线程直接销毁
import threading
import time
# print('hello')
# print('#######统计当前线程运行的个数######')
# print(threading.active_count())
def calm():
for i in range(10):
time.sleep(0.1)
print('第',i,'次冷静一下')
def smoke():
for j in range(10):
time.sleep(0.1)
print('第',j,'支烟')
if __name__ == '__main__':
# smoke()
# calm()
print('当前进程的线程个数',threading.active_count())
#创建多线程
thread_smoke = threading.Thread(target=smoke)
print('当前进程的线程个数', threading.active_count())
thread_calm = threading.Thread(target=calm)
#守护主线程
thread_smoke.setDaemon(True)
thread_calm.setDaemon(True)
#调用
thread_smoke.start()
thread_calm.start()
# time.sleep(0.1)
# thread_calm.join()
# thread_smoke.join()
print('当前进程的线程个数', threading.active_count())
print('主线程已经结束')
exit()
展示:
当前进程的线程个数 1
当前进程的线程个数 1
当前进程的线程个数 3
主线程已经结束
3、守护子线程,只有当子线程全部结束之后,主线程才结束
def calm():
for i in range(10):
time.sleep(0.1)
print('第',i,'次冷静一下')
def smoke():
for j in range(10):
time.sleep(0.1)
print('第',j,'支烟')
if __name__ == '__main__':
# smoke()
# calm()
print('当前进程的线程个数',threading.active_count())
#创建多线程
thread_smoke = threading.Thread(target=smoke)
print('当前进程的线程个数', threading.active_count())
thread_calm = threading.Thread(target=calm)
#调用
thread_smoke.start()
thread_calm.start()
# time.sleep(0.1)
thread_calm.join()
thread_smoke.join()
print('当前进程的线程个数', threading.active_count())
print('主线程已经结束')
exit()
展示:
当前进程的线程个数 1
当前进程的线程个数 1
第 0 支烟
第 0 次冷静一下
第 1 支烟
第 1 次冷静一下
第 2 支烟
第 2 次冷静一下
第 3 支烟
第 3 次冷静一下
第 4 次冷静一下
第 4 支烟
第 5 次冷静一下
第 5 支烟
第 6 支烟
第 6 次冷静一下
第 7 支烟
第 7 次冷静一下
第 8 次冷静一下
第 8 支烟
第 9 支烟
第 9 次冷静一下
当前进程的线程个数 1
主线程已经结束
4、子线程之间共享全局变量,就会造成资源的争抢问题
2.5 互斥锁的问题
问题:
因为线程之间共享全局变量,所以,在多线程几乎在同时运行的时候,几乎同时修改同一个全局变量的时候,就要进行控制
此时,需要互斥锁
当某个线程需要修改资源的时候,先将资源进行锁定,其他线程不能修改该线程
当线程修改完成之后,并且释放完互斥锁之后,其他的线程才可以使用
互斥锁保证在当前只有一个线程可以使用修改同一个资源
import threading
num = 0
def add_num1():
global num
for i in range(1000000):
#上锁
lock_flag = lock_obj.acquire(True)
if lock_flag:
num += 1
#释放锁
lock_obj.release()
print('子线程1已经完成,此时得到的num',num)
def add_num2():
global num
for i in range(1000000):
# 上锁
lock_flag = lock_obj.acquire(True)
if lock_flag:
num += 1
# 释放锁
lock_obj.release()
print('子线程2已经完成,此时得到的num',num)
if __name__ == '__main__':
lock_obj = threading.Lock()
thread1 = threading.Thread(target=add_num1)
thread2 = threading.Thread(target=add_num2)
thread1.start()
# import time
# time.sleep(5)
thread2.start()
注意点:
1、确保了关键代码只能由一个线程从头到尾完整的执行完成
2、阻止了多线程的并发操作,包含锁的代码其实还是单线程执行,效率下降了;
如果当前的程序出现了多个锁,可能会出现死锁的问题
2.6 死锁的问题
避免死锁的方法:
1、超时释放
2、设计的时候,避免死锁
2.7 线程安全
from queue import Queue
from Queue import Queue #python2
3、协程
微线程,由程序员调度,线程:CPU调度的
3.1 实现协程
import time
def work1():
for i in range(5):
print('work1',i)
yield
print('11111111111')
time.sleep(1)
def work2():
for i in range(5):
print('work2',i)
yield
time.sleep(1)
w1 = work1()
w2 = work2()
# while True:
next(w1)
next(w2)
next(w2)
next(w1)
注意点:
利用的是yeild这个属性,遇到yield会暂停执行,完成任务的切换
import time
from greenlet import greenlet
def work1():
for i in range(5):
print('work1',i)
g2.switch()
print('######已经切换了g2#####')
time.sleep(1)
def work2():
for i in range(5):
print('work2',i)
g1.switch()
time.sleep(1)
g1 = greenlet(work1)
print(g1)
g2 = greenlet(work2)
print(g2)
g2.switch()
注意点:
需要手动取切换协程
但是已经封装了协程了,比原始版本进化一点
import gevent
import time
from gevent import monkey
monkey.patch_all()
def work1():
for i in range(5):
print('work1',i,gevent.getcurrent())
time.sleep(1)
# gevent.sleep(1)
def work2():
for i in range(5):
print('work2',i,gevent.getcurrent())
time.sleep(1)
# gevent.sleep(1)
# g1 = gevent.spawn(work1)
# g2 = gevent.spawn(work2)
#
#
# g1.join()
# g2.join()
gevent.joinall([
gevent.spawn(work1),
gevent.spawn(work2)
])
4、网络通信概述
###4.1 网络的定义
网络:辅助双方或者多方能够连接在一起的工具,用来进行数据交换
协议:约定俗成的规定
tcp/ip协议:计算机遵守的网络通信协议
###4.2 端口
个数:2的16次方(65536)(0-65535)
端口是数据发出或接收的入口
端口的目的:通过端口号找到对应的进程,完成数据的通信
著名端口0-1023
动态端口:2014-65535
###4.3 IP
IP地址:标注在网络中标记一台计算机的一串数字,例如192.168.24.1 ,在本地局域网上是唯一的
注意:127.0.0.1 代表的是本机的IP地址
5、socket
简称:套接字,是进程之间进行通信的一种方式
实现了不同主机之间的进程通信,我们现在使用的大多数的服务:qq、浏览网页等基于socket来完成通信的
5.1 python 中socket
def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
# For user code address family and type values are IntEnum members, but
# for the underlying _socket.socket they're just integers. The
# constructor of _socket.socket converts the given argument to an
# integer automatically.
_socket.socket.__init__(self, family, type, proto, fileno)
self._io_refs = 0
self._closed = False
注意点:
1、family=AF_INET,表示的用于Internet之间的通信
2、type=SOCK_STREAM,是指流式套接字,用于tcp协议
3、type=SOCK_DGRAM数据报套接字,用于udp协议
5.2 udp发送数据
def sendto(self, data, flags=None, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
"""
sendto(data[, flags], address) -> count
Like send(data, flags) but allows specifying the destination address.
For IP sockets, the address is a pair (hostaddr, port).
"""
pass
注意点:
1、sendto()这个方法发送的数据,是bytes类型,普通字符串用encode()转化成bytes
2、address 用来指定对方的IP和端口,打包成元组
5.3 udp接收数据
def recvfrom(self, buffersize, flags=None): # real signature unknown; restored from __doc__
"""
recvfrom(buffersize[, flags]) -> (data, address info)
Like recv(buffersize, flags) but also return the sender's address info.
"""
pass
注意点:
1、返回值,返回(数据,发送该条信息的地址)
2、数据接收过来是bytes类型的,需要用decode()转换成普通的字符串
5.4 bind端口
def bind(self, address): # real signature unknown; restored from __doc__
"""
bind(address)
Bind the socket to a local address. For IP sockets, the address is a
pair (host, port); the host must refer to the local host. For raw packet
sockets the address is a tuple (ifname, proto [,pkttype [,hatype]])
"""
pass
socket_udp_client.bind(('',9000))
''代表本机的任何一个地址都可以
6、 UDP协议
5.1 特点
面向无连接的通讯协议
5.2 udp服务端的通信流程
1、创建一个socket套接字
2、绑定端口
3、接收服务端传递过来的请求
4、处理请求,发送数据给客户端
5、关闭套接字
5.3 UDP 客户端通信流程
1、创建套接字
2、发送请求(数据)
3、接收数据
4、关闭套接字
7、tcp协议
7.1 基于tcp协议的客户端
1、创建套接字
2、与服务端创建连接
3、发送数据
4、接收数据
5、关闭套接字
###7.2 基于tcp协议的服务端
1、创建socket套接字
2、bind绑定服务端的IP和端口
3、listen监听,使服务端的套接字从主动状态变成被动状态,等待连接
4、accept等待客户端的连接,运行之后是阻塞状态,直到有连接
5、收发数据
6、关闭套接字
8 、请求方式:
8.1 get
最常用的请求方式,用来向后端请求资源的,会将部分的参数拼接在URL地址上
http://tieba.baidu.com/f?kw=美食&ie=utf-8&pn=15000
?后面拼接的都是需要向后端传递的数据参数
8.2 post
也是常用的请求方式,但是它会将需要传递的数据,放置在post的请求体中,传递比get更安全,所以post请求中的数据可能是敏感数据:用户名和密码等
向指定资源提交数据进行处理请求(提交表单、上传文件),又可能导致新的资源的建立或原有资源的修改
8.3 delete
请求服务器删除request-URL所标示的资源*(请求服务器删除页面)
8.4 put
向指定资源位置上上传其最新内容(从客户端向服务器传送的数据取代指定文档的内容)
8.5 opions
返回服务器针对特定资源所支持的HTML请求方法 或web服务器发送*测试服务器功能(允许客户端查看服务器性能