Python网络编程(04)----进程/线程(2)

本文深入探讨Python中的进程、线程和协程概念,解析其内部机制与应用场景,通过实例展示如何利用multiprocessing和threading库进行并行编程,解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值