python27day23并发编程--------------------------------------------------------------------------------------------
多任务处理:使得计算机可以同时处理多个任务

程序:是一个指令的集合
进程:正在执行的程序;或者说:当你运行一个程序,你就启动了一个进程
编写完的代码,没有运行时,称为程序,正在运行的代码,称为进程
程序是死的(静态的),进程是活的(动态的)

操作系统轮流让各个任务交替执⾏ ,由于CPU的执⾏速度实在是太快了, 我们感觉就像所有任务都在同时执⾏⼀样

多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,互不影响

#模拟唱歌和跳舞同时进行:
from time import sleep
def sing():
for i in range(3):
print("正在唱歌")
dance()
sleep(1)
def dance():
print("正在跳舞")
if __name__ == '__main__':
sing()
结果: 正在唱歌
正在跳舞
正在唱歌
正在跳舞
正在唱歌
正在跳舞

多个进程,谁先开始运行,在CPU中运行多长时间,都是由操作系统的系统调度机制来决定的
创建进程的固定格式一:
from multiprocessing import Process
import time
num = 100
def p1():
global num
num = 1
print(num)
print("在进程一中",num)
def p2():
global num
num = 2
print("456")
print("在进程二中",num)
def p3():
global num
num = 3
print("789")
print("在进程三中",num)
if __name__ == '__main__':
a1 = Process(target=p1)
#位置参数元组:
a2 = Process(target=p2)
a3 = Process(target=p3)
a1.start()
a2.start()
a3.start()
a1.join(timeout=3)
a2.join()
a3.join()
结果: 1
在进程一中 1
456
在进程二中 2
789
在进程三中 3

程序开始运行时,首先会创建一个主进程

在主进程(父进程)下,我们可以创建新的进程(子进程),子进程依赖于主进程,如果主进程结束,程序会退出

Python提供了非常好用的多进程包multiprocessing,借助这个包,可以轻松完成从单进程到并发执行的转换

multiprocessing模块提供了⼀个Process类来创建⼀个进程对象

from multiprocessing import Process
def run(name):
print("子进程运行中,name = %s" %(name))
if __name__ == '__main__':
print("父进程启动")
p = Process(target=run,args=("test",))
# target表示调用对象,args表示调用对象的位置参数元组
# (注意:元组中只有一个元素时结尾要加,)
print("子进程将要执行")
p.start()
print(p.name)
p.join()
print("子进程结束")
结果: 父进程启动
子进程将要执行
Process-1
子进程运行中,name = test
子进程结束

#创建进程池:
from multiprocessing import Process,Pool
import time
def func():
print("这是一个函数")
time.sleep(3)
if __name__ == '__main__':
#括号当中是最大进程数,代表当前进程池对象,创建出的
po = Pool(6)
for i in range(3):
po.apply_async(func)
po.close()
po.join()

if __name__ == “__main__”:说明
一个python的文件有两种使用的方法,第一是直接作为程序执行,第二是import到其他的python程序中被调用(模块重用)执行。

因此if __name__ == 'main': 的作用就是控制这两种情况执行代码的过程,__name__ 是内置变量,用于表示当前模块的名字
在if __name__ == 'main': 下的代码只有在文件作为程序直接执行才会被执行,而import到其他程序中是不会被执行的

在 Windows 上,子进程会自动 import 启动它的这个文件,而在 import 的时候是会执行这些语句的。
如果不加if __name__ == "__main__":的话就会无限递归创建子进程
所以必须把创建子进程的部分用那个 if 判断保护起来
import 的时候 __name__ 不是 __main__ ,就不会递归运行了

Process(target , name , args)
参数介绍
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组,args=(1,)
name为子进程的名称

进程间通信:
多进程之间,默认是不共享数据的
通过Queue(队列Q)可以实现进程间的数据传递
Q本身是一个消息队列
如何添加消息(入队操作):
from multiprocessing import Process,Queue
def func1(q):
num = [1,2,3,4,5,6,7,8,9]
for i in num:
q.put(i)
def func2(q,q2):
num = []
while True:
if not q.empty():
n = q.get()
num.append(n)
q2.put(n)
else:
break
print(num)
def func3(q2):
num = []
while True:
if not q2.empty():
num.append(q2.get())
else:
break
print(num)
if __name__ == '__main__':
q1 = Queue(10)
q2 = Queue(20)
p1 = Process(target=func1,args=(q1,))
p2 = Process(target=func2,args=(q1,q2))
p3 = Process(target=func3,args=(q2,))
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
结果: [1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Process类常⽤⽅法:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate()(了解)强制终止进程p,不会进行任何清理操作
p.is_alive():如果p仍然运行,返回True.用来判断进程是否还在运行
p.join([timeout]):主进程等待p终止,timeout是可选的超时时间

Process类常⽤属性:
name: 当前进程实例别名, 默认为Process-N,N为从1开始递增的整数;
pid: 当前进程实例的PID值

进程池:用来创建多个进程

当需要创建的⼦进程数量不多时, 可以直接利⽤multiprocessing中的Process动态生成多个进程,
但如果是上百甚⾄上千个⽬标, ⼿动的去创建进程的⼯作量巨⼤,
此时就可以⽤到multiprocessing模块提供的Pool

初始化Pool时, 可以指定⼀个最⼤进程数, 当有新的请求提交到Pool中时,
如果池还没有满, 那么就会创建⼀个新的进程⽤来执⾏该请求;
但如果池中的进程数已经达到指定的最⼤值,那么该请求就会等待,直到池中有进程结束,
才会创建新的进程来执⾏

from multiprocessing import Pool
import random,time
def work(num):
print(random.random()*num)
time.sleep(3)
if __name__ == "__main__":
po = Pool(3) #定义一个进程池,最大进程数为3,默认大小为CPU核数
for i in range(10):
po.apply_async(work,(i,)) #apply_async选择要调用的目标,每次循环会用空出来的子进程去调用目标
po.close() #进程池关闭之后不再接收新的请求
po.join() #等待po中所有子进程结束,必须放在close后面

在多进程中:主进程一般用来等待,真正的任务都在子进程中执行

multiprocessing.Pool常⽤函数解析:
apply_async(func[, args[, kwds]]) : 使⽤⾮阻塞⽅式调⽤func(并⾏执⾏, 堵塞⽅式必须等待上⼀个进程退出才能执⾏下⼀个进程) , args为传递给func的参数列表, kwds为传递给func的关键字参数列表;
apply(func[, args[, kwds]]) (了解即可) 使⽤阻塞⽅式调⽤func
close(): 关闭Pool, 使其不再接受新的任务;
join(): 主进程阻塞, 等待⼦进程的退出, 必须在close或terminate之后使⽤;

可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递
初始化Queue()对象时(例如: q=Queue()) , 若括号中没有指定最⼤可接收的消息数量, 或数量为负值,
那么就代表可接受的消息数量没有上限
Queue.qsize(): 返回当前队列包含的消息数量
Queue.empty(): 如果队列为空, 返回True, 反之False
Queue.full(): 如果队列满了, 返回True,反之False
Queue.get([block[, timeout]]): 获取队列中的⼀条消息, 然后将其从列队中移除, block默认值为True
如果block使⽤默认值, 且没有设置timeout(单位秒) , 消息列队如果为空,
此时程序将被阻塞(停在读取状态) , 直到从消息列队读到消息为⽌,如果设置了timeout,
则会等待timeout秒, 若还没读取到任何消息, 则抛出"Queue.Empty"异常
如果block值为False, 消息列队如果为空, 则会⽴刻抛出“Queue.Empty”异常
python27day24并发编程--------------------------------------------------------------------------------------------
线程:实现多任务的另一种方式
一个进程中,也经常需要同时做多件事,就需要同时运行多个‘子任务’,这些子任务,就是线程
线程又被称为轻量级进程(lightweight process),是更小的执行单元

一个进程可拥有多个并行的(concurrent)线程,当中每一个线程,共享当前进程的资源

一个进程中的线程共享相同的内存单元/内存地址空间可以访问相同的变量和对象,而且它们从同一堆中分配对象通信、数据交换、同步操作

由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快

创建线程:
import threading
import time
threading.current_thread().name = "主线程"
print(threading.current_thread().name)
def func(num):
print(threading.current_thread().name,num) #打印线程名字和参数
time.sleep(5)
for i in range(5):
t1 = threading.Thread(target=func,name="线程1",args=("123",)) #创建线程对象
t1.start()
print(len(threading.enumerate())) #查看线程长度
print(threading.enumerate()) #查看所有线程
print("主进程结束")
结果: 主线程
线程1 123
线程1 123
线程1 123
线程1 123
线程1 123
6
[<_MainThread(主线程, started 9504)>, <Thread(线程1, started 10124)>, <Thread(线程1, started 1664)>,
<Thread(线程1, started 4108)>, <Thread(线程1, started 9896)>, <Thread(线程1, started 10008)>]
主进程结束

定义全局:
import threading
import time
threading.current_thread().name = "主线程"
num = 500
def func():
global num
print(threading.current_thread().name)
time.sleep(1)
num += 300
print("在线程一中",num)
def func1():
global num
time.sleep(1)
num += 1000
print("在线程二中",num)
t1 = threading.Thread(target=func)
t2 = threading.Thread(target=func1)
t1.start()
t2.start()
print("在主线程中",num)
结果: Thread-1
在主线程中 500
在线程二中 1500
在线程一中 1800

同时循环加:
import threading
import time
threading.current_thread().name = "主线程"
num = 500
def func():
global num
for i in range(100000):
num += 1
print("在线程一中",num)
def func1():
global num
for i in range(100000):
num += 1
print("在线程二中",num)
t1 = threading.Thread(target=func)
t2 = threading.Thread(target=func1)
t1.start()
t2.start()
print("在主线程中",num)
结果: 在线程一中 132177
在主线程中 166214
在线程二中 200500

线程和进程的区别:
进程是系统进⾏资源分配和调度的⼀个独⽴单位
进程在执⾏过程中拥有独⽴的内存单元, ⽽多个线程共享内存,从⽽极⼤地提⾼了程序的运⾏效率
⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程
线程是进程的⼀个实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独⽴运⾏的基本单位
线程⾃⼰基本上不拥有系统资源,只拥有⼀点在运⾏中必不可少的资源,但是它可与同属⼀个进程的其他的线程共享进程所拥有的全部资源
线程的划分尺度⼩于进程(资源⽐进程少),使得多线程程序的并发性⾼
线程不能够独⽴执⾏, 必须依存在进程中
线程和进程在使⽤上各有优缺点: 线程执⾏开销⼩,但不利于资源的管理和保护;⽽进程正相反

python的thread模块是⽐较底层的模块,在各个操作系统中表现形式不同(低级模块)
python的threading模块是对thread做了⼀些包装的, 可以更加⽅便的被使⽤(高级模块)
thread 有一些缺点,在threading 得到了弥补,所以我们直接学习threading
import threading
if __name__ == "__main__":
#任何进程默认会启动一个线程,这个线程称为主线程,主线程可以启动新的子线程
#current_thread():返回当前线程的实例
#.name :当前线程的名称
print('主线程%s启动' %(threading.current_thread().name))

线程的五种状态:
1、新状态:线程对象已经创建,还没有在其上调用start()方法。
2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的(可运行的),但是当前没有条件运行。但是如果某件事件出现,他可能返回到可运行状态。
5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程共享全局变量:
在⼀个进程内的所有线程共享全局变量, 多线程之间的数据共享(这点要⽐多进程要好)
缺点就是, 可能造成多个线程同时修改一个变量(即线程⾮安全),可能造成混乱

线程同步:
当多个线程⼏乎同时修改某⼀个共享数据的时候, 需要进⾏同步控制
线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引⼊互斥锁
互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性(原子性)
互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,
其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

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

创建锁和释放锁:
import threading
import time
threading.current_thread().name = "主线程"
num = 500
def func():
global num
lock1.acquire()
for i in range(100000):
num += 1
print("在线程一中",num)
lock1.release()
def func1():
global num
lock1.acquire()
for i in range(10000):
num += 1
print("在线程二中",num)
lock1.release()
lock1 = threading.Lock() #创建锁
t1 = threading.Thread(target=func)
t2 = threading.Thread(target=func1)
t1.start()
t2.start()
print("在主线程中",num)
结果: 在线程一中 100500
在线程二中 110500
在主线程中 110500

当一个线程调用Lock对象的acquire()方法获得锁时,这把锁就进入“locked”状态。
因为每次只有一个线程可以获得锁,所以如果此时另一个线程2试图获得这个锁,该线程2就会变为同步阻塞状态
直到拥有锁的线程1调用锁的release()方法释放锁之后,该锁进入“unlocked”状态。
线程调度程序继续从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态
一个线程有锁时别的线程只能在外面等着

互斥锁:
import threading
num = 0
def func1():
global num
for i in range(100):
lock1.acquire ()
num += 1
lock1.release()
print("在线程一中",num)
def func2():
global num
for i in range(100):
lock1.acquire()
num += 1
lock1.release()
print("在线程二中",num)
lock1 = threading.Lock()
lock1.acquire()
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
lock1.release()
结果: 在线程二中 168
在线程一中 200

线程里面创建线程:

线程共享当前进程的资源,但是在访问共享数据的时候不安全

线程同步(死锁)

死锁(错误情况,理解即可)
在线程间共享多个资源的时候, 如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源, 就会造成死锁

信号量:
信号量semaphore :用于控制一个时间点内线程进入数量的锁,信号量是用来控制线程并发数的

使用场景举例:在读写文件的时候,一般只有一个线程在写,而读可以有多个线程同时进行,
如果需要限制同时读文件的线程个数,这时候就可以用到信号量了
(如果用互斥锁,就是限制同一时刻只能有一个线程读取文件)

信号量:
import threading
import time
import random
def func():
lock1.acquire()
print("我完事了,您来")
time.sleep(random.randint(1,3))
lock1.release()
if __name__ == '__main__':
lock1 = threading.Semaphore(5)
for i in range(5): #创建5个线程
t1 = threading.Thread(target=func)
t1.start()
结果: 我完事了,您来
我完事了,您来
我完事了,您来
我完事了,您来
我完事了,您来

GIL全局解释器锁:
Cpython独有的锁,牺牲效率保证数据安全
GIL锁是一把双刃剑,它带来优势的同时也带来一些问题

首先:执行Python文件是什么过程?谁把进程起来的?
操作系统将你的应用程序从硬盘加载到内存。运行python文件,在内存中开辟一个进程空间,将你的Python解释器以及py文件加载进去,解释器运行py文件

Python解释器分为两部分,先将你的代码通过编译器编译成C的字节码,然后你的虚拟机拿到你的C的字节码,输出机器码,再配合操作系统把你的这个机器码扔给cpu去执行

你的py文件中有一个主线程,主线程做的就是这个过程。如果开多线程,每个线程都要进行这个过程

理想的情况:
三个线程,得到三个机器码,然后交由CPU,三个线程同时扔给三个CPU,然后同时进行,最大限度的提高效率,但是CPython多线程应用不了多核
CPython到底干了一件什么事情导致用不了多核?
Cpython在所有线程进入解释器之前加了一个全局解释器锁(GIL锁)。这个锁是互斥锁,是加在解释器上的,导致同一时间只有一个线程在执行所以你用不了多核

为什么这么干?
之前写python的人只有一个cpu。。。
所以加了一个锁,保证了数据的安全,而且在写python解释器时,更加好写了
为什么不取消这个锁?
解释器内部的管理全部是针对单线程写的

能不能不用Cpython?
官方推荐Cpython,处理速度快,相对其他解释器较完善。其他解释器比如pypy规则和漏洞很多

那我该怎么办?
虽然多线程无法应用多核,但是多进程可以应用多核(开销大)

Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么我还要学互斥锁?
首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
然后,我们可以得出结论:保护不同的数据就应该加不同的锁。
所以,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),
后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理
python27day25并发编程--------------------------------------------------------------------------------------------
同步:
同步调⽤:确定调用的顺序
提交一个任务,自任务开始运行直到此任务结束,我再提交下一个任务
按顺序购买四大名著
异步调⽤:不确定顺序
一次提交多个任务,然后我就直接执行下一行代码
你喊你朋友吃饭 ,你朋友说知道了 , 待会忙完去找你 ,你就去做别的了

给三个老师发布任务:
同步: 先告知第一个老师完成写书的任务,我原地等待,等他两个月之后完成了,我再发布下一个任务......

异步:直接将三个任务告知三个老师,我就忙我的我,直到三个老师完成之后,告知我

同步意味着顺序、统一的时间轴
场景1:是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,
再执行第二个事务,协同步调,按预定的先后次序进行运行

场景2:一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,
这是一种可靠的任务序列

import threading
import time
class Task1(threading.Thread):
def run(self):
while True:
if lock1.acquire():
print("----Task1-----")
time.sleep(1)
lock2.release()
class Task2(threading.Thread):
def run(self):
while True:
if lock2.acquire():
print("----Task2-----")
time.sleep(1)
lock3.release()
class Task3(threading.Thread):
def run(self):
while True:
if lock3.acquire():
print("----Task3-----")
time.sleep(1)
lock1.release()
lock1 = threading.Lock()
lock2 = threading.Lock()
lock2.acquire()
lock3 = threading.Lock()
lock3.acquire()
t1 = Task1()
t2 = Task2()
t3 = Task3()
t1.start()
t2.start()
t3.start()
结果: ----Task1-----
----Task2-----
----Task3-----
..............

线程同步-消息队列:
Python的Queue模块:实现了3种类型的队列来实现线程同步,包括:
FIFO(先⼊先出) 队列 Queue,
LIFO(后⼊先出) 栈 LifoQueue,
优先级队列 PriorityQueue
区别在于队列中条目检索的顺序不同
在FIFO队列中,按照先进先出的顺序检索条目
在LIFO队列中,最后添加的条目最先检索到(操作类似一个栈)
在优先级队列中,条目被保存为有序的(使用heapq模块)并且最小值的条目被最先检索
这些队列都实现了锁原语(可以理解为原⼦操作, 即要么不做, 要么就做完) , 能够在多线程中直接使⽤

现阶段只要求掌握其中一种,FIFO队列

⽣产者消费者模式:
⽣产者消费者模式
在线程世界⾥, ⽣产者就是⽣产数据的线程, 消费者就是消费数据的线程(做包子,吃包子)
经常会出现生产数据的速度大于消费数据的速度,或者生产速度跟不上消费速度

⽣产者消费者模式是通过⼀个容器(缓冲区)来解决⽣产者和消费者的强耦合问题
例如两个线程共同操作一个列表,一个放数据,一个取数据

⽣产者和消费者彼此之间不直接通讯, ⽽通过阻塞队列来进⾏通讯

import threading
import queue
def shengchan():
if rongqi.qsize() < 1500:
for i in range(1,1000):
rongqi.put(f"数据{i}")
def xiaofei():
if rongqi.qsize() >= 100:
for i in range(1,100):
print(rongqi.get())
rongqi = queue.Queue()
for i in range(1,501):
rongqi.put(f"初始数据{i}")
t1 = threading.Thread(target=shengchan)
t2 = threading.Thread(target=xiaofei)
t3 = threading.Thread(target=xiaofei)
t1.start()
t2.start()
t3.start()
结果: 初始数据1
初始数据2
初始数据3
初始数据4

线程同步--消息队列:
class queue.Queue(maxsize=0)
FIFO队列的构造器。maxsize为一个整数,表示队列的最大条目数,可用来限制内存的使用。
一旦队列满,插入将被阻塞直到队列中存在空闲空间。如果maxsize小于等于0,队列大小为无限。maxsize默认为0

异步一:
import time
import threading
num = 0
def func1():
global num
if lock.acquire(False):
for i in range(10):
num += 1
time.sleep(1)
print(num)
lock.release()
else:
for i in range(8):
time.sleep(1)
print("1干点别的!")
def func2():
global num
if lock.acquire(False):
for i in range(10):
num += 1
time.sleep(1)
print(num)
lock.release()
else:
for i in range(8):
time.sleep(1)
print("2干点别的!")
lock = threading.Lock()
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
结果: 1
2干点别的!
2
2干点别的!
2干点别的!
3
2干点别的!
4
5

异步则意味着乱序、效率优先的时间轴
处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、回调来通知调用者处理结果

对于I/O相关的程序来说,异步编程可以大幅度的提高系统的吞吐量,因为在某个I/O操作的读写过程中,
系统可以先去处理其它的操作(通常是其它的I/O操作)

不确定执行顺序

协程:
⽐线程更⼩的执⾏单元(微线程)
⼀个线程作为⼀个容器⾥⾯可以放置多个协程

只切换函数调用即可完成多线程,可以减少CPU的切换
协程⾃⼰主动让出CPU

from greenlet import greenlet
import time
def func():
sum = 0
while True:
for i in range(10):
sum += i
print(sum)
g2.switch()
time.sleep(1)
def func1():
sum = 0
while True:
for i in range(10):
sum += i
print(sum)
g1.switch()
time.sleep(1)
g1 = greenlet(func)
g2 = greenlet(func1)
g1.switch()
结果: 45
45
90
90
135
135
180
180
225
225
270

python还有⼀个⽐greenlet更强⼤的并且能够⾃动切换任务的模块 gevent
原理是当⼀个greenlet遇到IO(指的是input output 输⼊输出)操作时,
⽐如访问⽹络, 就⾃动切换到其他的greenlet, 等到IO操作完成,
再在适当的时候切换回来继续执⾏

进程线程的任务切换是由操作系统自行切换的,你自己不能控制
协程可以通过自己的程序(代码)来进行切换,自己能够控制
gevent只有遇到模块能够识别的IO操作的时候,程序才会进行任务切换,
实现并发效果,如果所有程序都没有IO操作,那么就基本属于串行执行了
gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,
等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,
经常使程序处于等待状态,有了gevent为我们自动切换协程,
就保证总有greenlet在运行,而不是等待IO

import gevent
def A():
while True:
print(".........A.........")
gevent.sleep(1)#用来模拟一个耗时操作
#gevent中:当一个协程遇到耗时操作会自动交出控制权给其他协程
def B():
while True:
print(".........B.........")
gevent.sleep(1)#每当遇到耗时操作,会自用转到其他协程
g1 = gevent.spawn(A) # 创建一个gevent对象(创建了一个协程),此时就已经开始执行A
g2 = gevent.spawn(B)
g1.join() #等待协程执行结束
g2.join() #会等待协程运行结束后再退出

守护进程:
import multiprocessing
import time
def func():
for i in range(10):
print("123")
time.sleep(1)
def fun():
for i in range(10):
print("守护!")
time.sleep(1)
if __name__ == '__main__':
t1 = multiprocessing.Process(target=func)
t2 = multiprocessing.Process(target=fun)
t2.daemon = True
t1.start()
t2.start()
t1.join()
结果: 123
守护!
123
守护!
123
守护!
123
守护!
123
守护!
123
守护!
123
守护!
123
守护!
123
守护!
123
守护!

守护进程是在主程序代码结束时就结束

守护线程:
import multiprocessing
import threading
import time
def func1():
for i in range(10):
print("A")
time.sleep(1)
def func2():
for i in range(10):
print("B")
time.sleep(1)
def func3():
for i in range(10):
print("C")
time.sleep(1)
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t3 = threading.Thread(target=func3)
t2.daemon = True
t1.start()
t2.start()
t3.start()


守护线程是在程序结束时才结束

网络基础:
如何在网络中唯一标识一台计算机? ip地址
同一台计算机上的多个程序如何共用网络而不冲突? 网络端口
不同的计算机通信怎么才能互相理解? 使用相同的协议

网络基础-IP地址:
IP地址: ⽤来在⽹络中标记⼀台电脑的⼀串数字, ⽐如192.168.1.1(c类); 在同一⽹络上是惟⼀的(用来标记唯一的一台电脑)
每⼀个IP地址包括两部分: ⽹络地址和主机地址
主机号0,255两个数不能使用(网络号、广播地址)

A类IP地址由1字节的⽹络地址和3字节主机地址组成, ⽹络地址的最⾼位必须是“0”,地址范围1.0.0.1-126.255.255.254可⽤的A类⽹络有126个, 每个⽹络能容纳1677214个主机
B类IP地址由2个字节的⽹络地址和2个字节的主机地址组成, ⽹络地址的最⾼位必须是“10”,地址范围128.1.0.1-191.255.255.254 可⽤的B类⽹络有16384个, 每个⽹络能容纳65534主机
C类IP地址由3字节的⽹络地址和1字节的主机地址组成, ⽹络地址的最⾼位必须是“110”范围192.0.1.1-223.255.255.254 C类⽹络可达2097152个, 每个⽹络能容纳254个主机
D类IP地址第⼀个字节以“1110”开始, 它是⼀个专⻔保留的地址。它并不指向特定的⽹络, ⽬前这⼀类地址被⽤在多点⼴播(一对多) 中多点⼴播地址⽤来⼀次寻址⼀组计算机 地址范围224.0.0.1-239.255.255.254
E类IP地址以“1111”开始, 为将来使⽤保留 E类地址保留, 仅作实验和开发⽤

私有IP:本地局域网上的IP,专门为组织机构内部使用
在这么多⽹络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:局域网通信,内部访问,不能在外网公用。私有IP禁止出现在Internet中,来自于私有IP的流量全部都会阻止并丢掉
公有IP:全球访问
IP地址127. 0. 0. 1⽤于回路测试
测试当前计算机的网络通信协议
如: 127.0.0.1可以代表本机IP地址, ⽤ http://127.0.0.1 就可以测试本机中配置的Web服务器
常用来ping 127.0.0.1来看本地ip/tcp正不正常,如能ping通即可正常使用

⼦⽹掩码:是我们测量两个IP是否属于同一个网段的工具
⼦⽹掩码不能单独存在, 它必须结合IP地址⼀起使⽤
⼦⽹掩码只有⼀个作⽤, 就是将某个IP地址划分成⽹络地址和主机地址两部分
⼦⽹掩码的设定必须遵循⼀定的规则:与IP地址相同, ⼦⽹掩码的长度也是32位,左边是⽹络位, ⽤⼆进制数字“1”表示;右边是主机位, ⽤⼆进制数字“0”表示
假设IP地址为“192.168.1.1”⼦⽹掩码为“255.255.255.0”。其中, “1”有24个, 代表与此相对应的IP地址左边24位是⽹络号;“0”有8个, 代表与此相对应的IP地址右边8位是主机号