《Python网络编程04》----进程/线程(2)
一、什么是进程、线程和协程?
进程:进程是系统进行资源分配和调度的单位。是具有一定独立功能的程序关于某个数据集合上的一次运行活动,每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程占据独立的内存,所以上下文进程之间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对稳定安全。
线程:线程是进程的一个实体,是CPU调度和分派的基本单位。它比进程更小,可独立运行,可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程之间通信可以共享内存,并且上下文切换很快,资源开销较少,但比进程稳定性差,容易丢失数据。
协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,上下文切换较快。
二、区别:
进程与线程比较:
1)地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。
2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。
3)线程是处理器CPU调度的基本单位,进程不是。
4)二者均可以 并发执行。
5)每个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依存应用程序,由应用程序提供多个线程执行控制。
协程与线程比较:
1)一个线程可以多协程,一个进程也可以单独拥有多个协程,这样python则支持多核CPU。
2)线程进程都是同步机制,协程属于异步。
3)协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
三、Python的代码实现
1)多进程----调用multiprocessing库:
案例一:创建多进程执行多任务
# coding:utf-8
# 创建多进程执行多任务
import time
import multiprocessing
def test1():
while True:
print("-----1-----")
time.sleep(1)
def test2():
while True:
print("-----2-----")
time.sleep(1)
def main():
p1 = multiprocessing.Process(target = test1)
p2 = multiprocessing.Process(target = test2)
p1.start()
p2.start()
if __name__ == "__main__":
main()
执行结果:
-----2-----
-----1-----
-----2-----
-----2-----
-----1-----
-----2-----
-----1-----
-----2-----
-----1-----
案例二:利用队列实现多进程之间的通信
# coding:utf-8
#利用队列实现多进程之间的通信
import multiprocessing
from multiprocessing import Queue
def ceshi():
q=Queue(3)
q.put(111)
q.put("123")
q.put([1,2,3])
print(q.get())
print(q.get())
print(q.get())
print(q.empty())
print(q.full())
print(q.get())
def download_from_web(q):
"""下载数据"""
# 模拟从网上下载的数据
data = [11,22,33,44]
# 向队列中写入数据
for temp in data:
q.put(temp)
print("---下载器已经下载完了数据并且存入队列中")
def analysis_data(q):
"""数据处理"""
waitting_analysis_data = list()
# 从队列中获取数据
while True:
data = q.get()
waitting_analysis_data.append(data)
if q.empty():
break
print(waitting_analysis_data)
def main():
# 1. 创建一个队列
q = Queue()
# 2.创建多个进程,将队列的引用当做实参进行传递到里面
p1 = multiprocessing.Process(target=download_from_web,args=(q,))
p2 = multiprocessing.Process(target=analysis_data,args=(q,))
p1.start()
p2.start()
if __name__ == "__main__":
main()
执行结果:
---下载器已经下载完了数据并且存入队列中
[11, 22, 33, 44]
案例三:进程池的使用
# coding:utf-8
# 进程池
from multiprocessing import Pool
import os,time,random
def worker(msg):
t_start = time.time()
print("%s开始执行,进程号是%d" %(msg,os.getpid()))
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"执行完毕,耗时%.2f" %(t_stop-t_start))
def main():
po = Pool(3)
for i in range(10):
po.apply_async(worker,(i,))
print("------start------")
po.close()
po.join()
print("------end------")
if __name__ == "__main__":
main()
执行结果(可以看到进程的重复利用):
------start------
0开始执行,进程号是9832
1开始执行,进程号是7308
2开始执行,进程号是2248
1 执行完毕,耗时0.13
3开始执行,进程号是7308
0 执行完毕,耗时0.51
4开始执行,进程号是9832
2 执行完毕,耗时0.85
5开始执行,进程号是2248
5 执行完毕,耗时0.49
6开始执行,进程号是2248
3 执行完毕,耗时1.30
7开始执行,进程号是7308
4 执行完毕,耗时1.13
8开始执行,进程号是9832
7 执行完毕,耗时0.37
9开始执行,进程号是7308
6 执行完毕,耗时0.81
8 执行完毕,耗时1.27
9 执行完毕,耗时1.39
------end------
案例四:使用多进程拷贝文件夹
注意:写代码时,先从简单的功能开始,然后再逐步完善!
# coding:utf-8
# 本节的案例是要做一个:拷贝文件夹的小应用,然后不断的改善需求
import multiprocessing
import os
def copy_file(q,file_name,old_folder_name,new_folder_name):
"""完成文件的复制"""
# print("====模拟copy文件:从 %s---->到 %s 文件名是: %s" % (old_folder_name,new_folder_name,file_name))
# 读取原文件夹中的内容
old_f = open(old_folder_name + "/" + file_name, "rb")
content= old_f.read()
old_f.close()
new_f = open(new_folder_name + "/" + file_name, "wb")
new_f.write(content)
new_f.close()
# 如果拷贝完了文件,那么就向队列中写入一个消息,表示已经完成
q.put(file_name)
def main():
# 1. 获取用户要copy的文件夹的名字
old_folder_name = input("请输入要copy的文件夹的名字: ")
# 2. 创建一个新的文件夹(如果已经创建了文件夹,那么就不需要重新创建)
try:
new_folder_name = old_folder_name + "[附件]"
os.mkdir(new_folder_name)
except:
pass
# 3. 获取文件夹的所有的待copy的文件名字 listdir()
file_names = os.listdir(old_folder_name)
# print(file_names)
# 4. 创建进程池
po = multiprocessing.Pool(5)
# 5. 创建一个队列,目的是完成进度条的显示。也就是一个任务完成,告诉进程
q = multiprocessing.Manager().Queue()
# 6. 向进程池中添加 copy 文件的任务
for file_name in file_names:
po.apply_async(copy_file, args=(q,file_name,old_folder_name,new_folder_name))
po.close()
# po.join()
all_file_num = len(file_names)
copy_ok_num = 0
while True:
file_name = q.get()
# print("已经完成copy: %s" % file_name)
copy_ok_num +=1
print("\r拷贝的进度为 %.2f %%" %(copy_ok_num*100/all_file_num),end="")
if copy_ok_num >= all_file_num:
break
print()
if __name__ == "__main__":
main()
2)多线程----调用threading库
案例一:利用“继承”实现多线程多任务
# coding:utf-8
# 也可以换另外一种方式来实现进程的运行
import threading
import time
class MyThread(threading.Thread):
def run(self): # 必须定义 run
self.login()
# print(self.name)
self.register()
for i in range(5):
time.sleep(1)
msg = "I'm"+self.name+'@'+str(i)
print(msg)
def login(self):
print("这是登录...")
def register(self):
print("这是注册的代码...")
if __name__ == "__main__":
t = MyThread()
t.start()
运行结果:
这是登录...
这是注册的代码...
I'mThread-1@0
I'mThread-1@1
I'mThread-1@2
I'mThread-1@3
I'mThread-1@4
案例二:多线程可以共享全局变量?
# coding:utf-8
# 子线程是否共享全局变量? 多线程可以共享全局变量
import threading
import time
nums=100
def test1():
global nums
nums+=1
print("----in test1----nums=%d--" %nums)
def test2():
print("----in test2----nums=%d--" %nums)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print("----in main----nums=%d--" %nums)
if __name__ == "__main__":
main()
运行结果:
----in test1----nums=101--
----in test2----nums=101--
----in main----nums=101—
案例三:多线程出现资源竞争问题
资源竞争问题:多个线程对同一资源进行操作时,通常会产生进程,比如一个线程往消息队列插入数据,另一个线程从消息队列取出数据。当消息队列满时,插入消息的队列需要sleep几个毫秒,把时间片让出给取消息的线程,当消息队列为空时,取消息队列的线程需要sleep几个毫秒,把时间片让给插入消息的线程。如果不这样做,则会出现某个线程独占资源,最终导致另一个线程死等状态,引发一系列的问题。
例如:
# coding:utf-8
# 多线程中的资源竞争的体现
import threading
import time
g_num=0
def test1(num):
global g_num
for i in range(num):
g_num+=1
print("g_num in test1 is %d" % g_num)
def test2(num):
global g_num
for i in range(num):
g_num+=1
print("g_num in test2 is %d" % g_num)
def main():
t1=threading.Thread(target=test1,args=(1000000,))
t2=threading.Thread(target=test2,args=(1000000,))
t1.start()
t2.start()
print("g_num in main is %d" % g_num)
if __name__ == "__main__":
main()
运行结果:
g_num in main is 141918
g_num in test1 is 1239860
g_num in test2 is 1300362
出现资源竞争的问题该如何解决呢?
案例四:加锁(互斥锁)
互斥锁:用来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证任一时刻,只有一个线程访问该对象。
# coding:utf-8
# 出现资源竞争后,可以用互斥锁来处理
import threading
import time
g_num=0
mutex = threading.Lock()
def test1(num):
global g_num
# 上锁,如果之前没有被上锁,那么此时上锁成功
# 如果上锁之前已经被上锁,那么此时会堵塞在这里,直到这个锁被解开位置
mutex.acquire()
for i in range(num):
g_num+=1
# 解锁
mutex.release()
print("g_num in test1 is %d" % g_num)
def test2(num):
global g_num
mutex.acquire()
for i in range(num):
g_num+=1
mutex.release()
print("g_num in test2 is %d" % g_num)
def main():
t1=threading.Thread(target=test1,args=(1000000,))
t2=threading.Thread(target=test2,args=(1000000,))
t1.start()
t2.start()
# 等待上面2个线程执行完毕...
time.sleep(1)
print("g_num in main thread is %d" % g_num)
if __name__ == "__main__":
main()
运行结果:
g_num in test1 is 1000000
g_num in test2 is 2000000
g_num in main thread is 2000000
案例五:更改互斥锁的位置,让锁涉及的代码越少越好
# coding:utf-8
# 出现资源竞争后,可以用互斥锁来处理(更改锁的位置,锁涉及的代码越少越好)
# 这个结果最终是2000000,谁先上锁谁做事
import threading
import time
g_num=0
mutex = threading.Lock()
def test1(num):
global g_num
# 上锁,如果之前没有被上锁,那么此时上锁成功
# 如果上锁之前已经被上锁,那么此时会堵塞在这里,直到这个锁被解开位置
for i in range(num):
mutex.acquire()
g_num+=1
# 解锁
mutex.release()
print("g_num in test1 is %d" % g_num)
def test2(num):
global g_num
for i in range(num):
mutex.acquire()
g_num+=1
mutex.release()
print("g_num in test2 is %d" % g_num)
def main():
t1=threading.Thread(target=test1,args=(1000000,))
t2=threading.Thread(target=test2,args=(1000000,))
t1.start()
t2.start()
# 等待上面2个线程执行完毕...
time.sleep(3)
print("g_num in main thread is %d" % g_num)
if __name__ == "__main__":
main()
运行结果(最终结果是2000000):
g_num in test2 is 1941324
g_num in test1 is 2000000
g_num in main thread is 2000000