30、Python并发编程:线程、进程与守护进程的深入解析

Python并发编程:线程、进程与守护进程的深入解析

在Python编程中,并发处理是一个非常重要的话题,它可以帮助我们更高效地利用系统资源,提高程序的执行效率。本文将深入探讨Python中线程、进程的使用方法,以及如何将程序转化为守护进程。

线程的使用

线程是Python中实现并发的一种方式,通过使用线程,我们可以让程序同时执行多个任务。下面我们将介绍几种线程的使用场景。

多线程ARP扫描
worker.start()
#spawn pool of arping threads
for i in range(num_arp_threads):
    worker = Thread(target=arping, args=(i, out_queue))
    worker.setDaemon(True)
    worker.start()
print "Main Thread Waiting"
#ensures that program does not exit until both queues have been emptied
in_queue.join()
out_queue.join()
print "Done"

上述代码通过创建多个线程来进行ARP扫描。具体步骤如下:
1. 循环创建指定数量的线程,每个线程的目标函数为 arping ,并传入相应的参数。
2. 将线程设置为守护线程,确保主线程退出时,子线程也随之退出。
3. 启动所有线程。
4. 主线程等待输入队列和输出队列中的任务完成。
5. 输出完成信息。

运行该代码,输出结果如下:

python2.5 ping_thread_basic_2.py
Main Thread Waiting
Thread 0: Pinging 10.0.1.1
Thread 1: Pinging 10.0.1.3
Thread 2: Pinging 10.0.1.11
Thread 0: Pinging 10.0.1.51
IP Address: 10.0.1.1 | Mac Address: [00:00:00:00:00:01]
IP Address: 10.0.1.51 | Mac Address: [00:00:00:80:E8:02]
IP Address: 10.0.1.3 | Mac Address: [00:00:00:07:E4:03]
10.0.1.11: did not respond
Done

使用队列模块可以让线程的使用更加简单和安全,是一种非常实用的技术。

线程定时延迟

Python的 threading.Timer 模块可以实现线程的定时延迟执行。以下是一个示例代码:

#!/usr/bin/env python
from threading import Timer
import sys
import time
import copy

#simple error handling
if len(sys.argv) != 2:
    print "Must enter an interval"
    sys.exit(1)

#our function that we will run
def hello():
    print "Hello, I just got called after a %s sec delay" % call_time

#we spawn our time delayed thread here
delay = sys.argv[1]
call_time = copy.copy(delay)    #we copy the delay to use later
t = Timer(int(delay), hello)
t.start()

#we validate that we are not blocked, and that the main program continues
print "waiting %s seconds to run function" % delay
for x in range(int(delay)):
    print "Main program is still running for %s more sec" % delay
    delay = int(delay) - 1
    time.sleep(1)

该代码的执行步骤如下:
1. 检查命令行参数是否包含延迟时间,如果不包含则输出错误信息并退出。
2. 定义要延迟执行的函数 hello
3. 获取命令行传入的延迟时间,并复制一份用于后续输出。
4. 创建一个 Timer 对象,指定延迟时间和要执行的函数。
5. 启动 Timer 对象。
6. 主线程继续执行,输出等待信息,并每秒输出剩余等待时间。

运行该代码,输出结果如下:

[ngift@Macintosh-6][H:10468][J:0]# python thread_timer.py 5
waiting 5 seconds to run function
Main program is still running for 5 more sec
Main program is still running for 4 more sec
Main program is still running for 3 more sec
Main program is still running for 2 more sec
Main program is still running for 1 more sec
Hello, I just got called after a 5 sec delay

可以看到,主线程在延迟时间内继续执行,而延迟函数在指定时间后被调用。

线程事件处理

我们可以结合延迟线程和事件循环来实现更复杂的功能,例如监控两个目录的文件变化,并在变化发生时执行同步操作。以下是一个示例代码:

#!/usr/bin/env python
from threading import Timer
import sys
import time
import copy
import os
from subprocess import call

class EventLoopDelaySpawn(object):
    """An Event Loop Class That Spawns a Method in a Delayed Thread"""
    def __init__(self, poll=10,
                        wait=1,
                        verbose=True,
                        dir1="/tmp/dir1",
                        dir2="/tmp/dir2"):
        self.poll = int(poll)
        self.wait = int(wait)
        self.verbose = verbose
        self.dir1 = dir1
        self.dir2 = dir2

    def poller(self):
        """Creates Poll Interval"""
        time.sleep(self.poll)
        if self.verbose:
            print "Polling at %s sec interval" % self.poll

    def action(self):
        if self.verbose:
            print "waiting %s seconds to run Action" % self.wait
        ret = call("rsync -av --delete %s/ %s" % (self.dir1, self.dir2), shell=True)

    def eventHandler(self):
        #if two directories contain same file names
        if os.listdir(self.dir1) != os.listdir(self.dir2):
            print os.listdir(self.dir1)
            t = Timer((self.wait), self.action)
            t.start()
            if self.verbose:
                print "Event Registered"
        else:
            if self.verbose:
                print "No Event Registered"

    def run(self):
        """Runs an event loop with a delayed action method"""
        try:
            while True:
                self.eventHandler()
                self.poller()
        except Exception, err:
            print "Error: %s " % err
        finally:
            sys.exit(0)

E = EventLoopDelaySpawn()
E.run()

该代码的执行流程如下:
1. 初始化 EventLoopDelaySpawn 类,设置轮询时间、延迟时间、是否输出详细信息以及要监控的两个目录。
2. poller 方法用于设置轮询间隔,在指定时间内休眠。
3. action 方法用于执行同步操作,使用 rsync 命令同步两个目录。
4. eventHandler 方法检查两个目录的文件列表是否相同,如果不同则创建一个延迟线程执行 action 方法。
5. run 方法是事件循环的主函数,不断调用 eventHandler poller 方法。

线程延迟可以创建可取消的条件未来操作,例如在发现另一个事件时取消延迟线程。

进程的使用

虽然线程可以实现并发,但在某些情况下,使用进程可能更合适。由于Python的全局解释器锁(GIL),同一时间只有一个线程可以真正运行,并且只能使用一个处理器。因此,对于需要大量使用CPU的任务,使用进程可以更好地利用多核处理器。

处理模块介绍

处理模块是一个用于Python的包,它支持使用标准库的线程模块API来创建进程。以下是一个简单的示例代码:

#!/usr/bin/env python
from processing import Process, Queue
import time

def f(q):
    x = q.get()
    print "Process number %s, sleeps for %s seconds" % (x,x)
    time.sleep(x)
    print "Process number %s finished" % x

q = Queue()
for i in range(10):
    q.put(i)
    i = Process(target=f, args=[q])
    i.start()

print "main process joins on queue"
i.join()
print "Main Program finished"

该代码的执行步骤如下:
1. 定义一个函数 f ,从队列中获取一个值,并休眠相应的时间。
2. 创建一个队列 q ,并将0到9的数字放入队列中。
3. 循环创建10个进程,每个进程的目标函数为 f ,并传入队列 q
4. 启动所有进程。
5. 主线程等待队列中的任务完成。
6. 输出完成信息。

运行该代码,输出结果如下:

[ngift@Macintosh-7][H:11199][J:0]# python processing1.py
Process number 0, sleeps for 0 seconds
Process number 0 finished
Process number 1, sleeps for 1 seconds
Process number 2, sleeps for 2 seconds
Process number 3, sleeps for 3 seconds
Process number 4, sleeps for 4 seconds
main process joins on queue
Process number 5, sleeps for 5 seconds
Process number 6, sleeps for 6 seconds
Process number 8, sleeps for 8 seconds
Process number 7, sleeps for 7 seconds
Process number 9, sleeps for 9 seconds
Process number 1 finished
Process number 2 finished
Process number 3 finished
Process number 4 finished
Process number 5 finished
Process number 6 finished
Process number 7 finished
Process number 8 finished
Process number 9 finished
Main Program finished
基于进程的Ping扫描

我们可以使用处理模块实现基于进程的Ping扫描,以下是一个示例代码:

#!/usr/bin/env python
from processing import Process, Queue, Pool
import time
import subprocess
from IPy import IP
import sys

q = Queue()
ips = IP("10.0.1.0/24")

def f(i,q):
    while True:
        if q.empty():
            sys.exit()
        print "Process Number: %s" % i
        ip = q.get()
        ret = subprocess.call("ping -c 1 %s" % ip,
                        shell=True,
                        stdout=open('/dev/null', 'w'),
                        stderr=subprocess.STDOUT)
        if ret == 0:
            print "%s: is alive" % ip
        else:
            print "Process Number: %s didn’t find a response for %s " % (i, ip)

for ip in ips:
    q.put(ip)

#q.put("192.168.1.1")
for i in range(50):
    p = Process(target=f, args=[i,q])
    p.start()

print "main process joins on queue"
p.join()
print "Main Program finished"

该代码的执行步骤如下:
1. 创建一个队列 q ,并将 10.0.1.0/24 网段的所有IP地址放入队列中。
2. 定义一个函数 f ,从队列中获取一个IP地址,并使用 ping 命令进行测试。
3. 循环创建50个进程,每个进程的目标函数为 f ,并传入进程编号和队列 q
4. 启动所有进程。
5. 主线程等待队列中的任务完成。
6. 输出完成信息。

与线程代码相比,进程代码的API非常相似,但每个进程在一个无限循环中从队列中获取任务,当队列为空时,进程退出。

进程调度

在Python中,我们可以使用 cron 来调度进程。许多POSIX系统的 cron 有一个很好的新特性,即调度目录。我们可以将Python脚本放在 /etc/cron.daily /etc/cron.hourly /etc/cron.monthly /etc/cron.weekly 这四个默认目录之一, cron 会自动在指定时间执行这些脚本。

以下是一个使用Python脚本生成磁盘使用报告并发送邮件的示例代码:

import smtplib
import subprocess
import string

p = subprocess.Popen("df -h", shell=True, stdout=subprocess.PIPE)
MSG = p.stdout.read()
FROM = "guru-python-sysadmin@example.com"
TO = "staff@example.com"
SUBJECT = "Nightly Disk Usage Report"

msg = string.join((
    "From: %s" % FROM,
    "To: %s" % TO,
    "Subject: %s" % SUBJECT,
    "",
    MSG), "\r\n")

server = smtplib.SMTP('localhost')
server.sendmail(FROM, TO, msg)
server.quit()

该代码的执行步骤如下:
1. 使用 subprocess.Popen 执行 df -h 命令,并读取输出结果。
2. 设置发件人、收件人和邮件主题。
3. 将发件人、收件人、主题和邮件内容拼接成一个字符串。
4. 创建一个SMTP服务器对象,并连接到本地SMTP服务器。
5. 发送邮件。
6. 关闭SMTP服务器连接。

将该脚本放在 /etc/cron.daily/nightly_disk_report.py cron 会每天执行该脚本并发送磁盘使用报告。

守护进程

在Unix系统中,守护进程是一种在后台运行的任务,没有控制终端。以下是一个将程序转化为守护进程的示例代码:

import sys, os

def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
    # Perform first fork.
    try:
        pid = os.fork( )
        if pid > 0:
            sys.exit(0) # Exit first parent.
    except OSError, e:
        sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
        sys.exit(1)

    # Decouple from parent environment.
    os.chdir("/")
    os.umask(0)
    os.setsid( )

    # Perform second fork.
    try:
        pid = os.fork( )
        if pid > 0:
            sys.exit(0) # Exit second parent.
    except OSError, e:
        sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
        sys.exit(1)

    # The process is now daemonized, redirect standard file descriptors.
    for f in sys.stdout, sys.stderr: f.flush( )
    si = file(stdin, 'r')
    so = file(stdout, 'a+')
    se = file(stderr, 'a+', 0)
    os.dup2(si.fileno( ), sys.stdin.fileno( ))
    os.dup2(so.fileno( ), sys.stdout.fileno( ))
    os.dup2(se.fileno( ), sys.stderr.fileno( ))

该代码的执行流程如下:
1. 第一次 fork :创建一个子进程,父进程退出。
2. 与父进程环境解耦:将工作目录切换到根目录,设置文件创建掩码为0,创建一个新的会话。
3. 第二次 fork :再次创建一个子进程,父进程退出。
4. 重定向标准输入、输出和错误流:将标准输入、输出和错误流重定向到指定的文件。

以下是一个使用守护进程监控时间的示例代码:

from daemonize import daemonize
import time
import sys

def mod_5_watcher():
    start_time = time.time()
    end_time = start_time + 20
    while time.time() < end_time:
        now = time.time()
        if int(now) % 5 == 0:
            sys.stderr.write('Mod 5 at %s\n' % now)
        else:
            sys.stdout.write('No mod 5 at %s\n' % now)
        time.sleep(1)

if __name__ == '__main__':
    daemonize(stdout='/tmp/stdout.log', stderr='/tmp/stderr.log')
    mod_5_watcher()

该代码的执行步骤如下:
1. 调用 daemonize 函数将程序转化为守护进程,并指定标准输出和错误输出的文件。
2. mod_5_watcher 函数用于监控时间,在接下来的20秒内,每秒检查一次时间,如果时间能被5整除,则输出到标准错误流,否则输出到标准输出流。

运行该脚本后,我们可以在 /tmp/stdout.log /tmp/stderr.log 文件中查看输出结果。

通过本文的介绍,我们了解了Python中线程、进程和守护进程的使用方法。线程适用于I/O密集型任务,而进程适用于CPU密集型任务。守护进程可以让程序在后台持续运行,不受终端控制。在实际应用中,我们可以根据具体需求选择合适的并发方式。

Python并发编程:线程、进程与守护进程的深入解析

线程、进程与守护进程的综合对比

为了更清晰地了解线程、进程和守护进程的特点和适用场景,我们可以通过以下表格进行对比:

类型 优点 缺点 适用场景
线程 轻量级,创建和销毁开销小;共享全局状态,通信方便 受GIL限制,同一时间只有一个线程能真正运行;线程安全问题复杂 I/O密集型任务,如网络请求、文件读写等
进程 可利用多核处理器,能真正实现并行计算;独立运行,互不干扰 创建和销毁开销大;进程间通信复杂 CPU密集型任务,如大数据处理、科学计算等
守护进程 后台运行,不依赖控制终端;可长期稳定运行 管理和调试相对复杂 需要长期运行的服务,如监控服务、日志服务等
深入理解线程与进程的通信机制
线程通信

线程之间可以通过共享全局变量、队列等方式进行通信。在前面的多线程ARP扫描示例中,我们使用了队列来传递数据。队列是一种线程安全的数据结构,可以避免多个线程同时访问共享资源时产生的竞争问题。以下是一个简单的线程通信示例:

from threading import Thread
from queue import Queue

def producer(q):
    for i in range(5):
        q.put(i)
        print(f"Produced {i}")

def consumer(q):
    while True:
        item = q.get()
        if item is None:
            break
        print(f"Consumed {item}")
        q.task_done()

q = Queue()
p = Thread(target=producer, args=(q,))
c = Thread(target=consumer, args=(q,))

p.start()
c.start()

p.join()
q.put(None)
c.join()

print("All tasks are done.")

该示例中, producer 线程负责向队列中放入数据, consumer 线程负责从队列中取出数据进行处理。通过队列,两个线程可以安全地进行数据交换。

进程通信

进程之间的通信相对复杂,常见的方式有管道、队列、共享内存等。在前面的基于进程的Ping扫描示例中,我们使用了队列来传递IP地址。以下是一个使用 multiprocessing.Queue 进行进程通信的示例:

from multiprocessing import Process, Queue

def sender(q):
    for i in range(5):
        q.put(i)
        print(f"Sent {i}")

def receiver(q):
    while True:
        item = q.get()
        if item is None:
            break
        print(f"Received {item}")

q = Queue()
s = Process(target=sender, args=(q,))
r = Process(target=receiver, args=(q,))

s.start()
r.start()

s.join()
q.put(None)
r.join()

print("All tasks are done.")

该示例中, sender 进程负责向队列中放入数据, receiver 进程负责从队列中取出数据进行处理。通过队列,两个进程可以安全地进行数据交换。

优化并发编程的技巧
线程池与进程池的使用

在实际应用中,频繁创建和销毁线程或进程会带来较大的开销。为了提高效率,我们可以使用线程池和进程池来管理线程和进程的生命周期。以下是一个使用 concurrent.futures 模块创建线程池的示例:

import concurrent.futures
import time

def task(n):
    time.sleep(1)
    return n * n

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    results = [executor.submit(task, i) for i in range(5)]
    for future in concurrent.futures.as_completed(results):
        print(future.result())

该示例中,我们使用 ThreadPoolExecutor 创建了一个最大工作线程数为3的线程池。通过 submit 方法向线程池提交任务,并使用 as_completed 方法获取任务的执行结果。

异步编程

Python的 asyncio 库提供了异步编程的支持,可以在单线程中实现高效的并发。以下是一个简单的异步编程示例:

import asyncio

async def task(n):
    await asyncio.sleep(1)
    return n * n

async def main():
    tasks = [task(i) for i in range(5)]
    results = await asyncio.gather(*tasks)
    for result in results:
        print(result)

asyncio.run(main())

该示例中,我们使用 asyncio 库创建了异步任务,并使用 gather 方法同时运行多个任务。异步编程可以避免线程和进程的创建和销毁开销,提高程序的性能。

实际应用案例分析
网络爬虫

网络爬虫是一个典型的I/O密集型任务,适合使用线程或异步编程来提高效率。以下是一个简单的网络爬虫示例:

import requests
import concurrent.futures

urls = [
    "https://www.example.com",
    "https://www.example.org",
    "https://www.example.net"
]

def fetch(url):
    response = requests.get(url)
    return response.text

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    results = [executor.submit(fetch, url) for url in urls]
    for future in concurrent.futures.as_completed(results):
        print(len(future.result()))

该示例中,我们使用线程池同时发起多个网络请求,提高了爬虫的效率。

数据分析

数据分析是一个典型的CPU密集型任务,适合使用进程来利用多核处理器。以下是一个简单的数据分析示例:

import multiprocessing

def analyze(data):
    # 模拟数据分析操作
    return sum(data)

data_chunks = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

with multiprocessing.Pool(processes=3) as pool:
    results = pool.map(analyze, data_chunks)
    print(sum(results))

该示例中,我们使用进程池同时对多个数据块进行分析,提高了数据分析的效率。

总结与展望

通过本文的深入探讨,我们对Python中的线程、进程和守护进程有了更全面的了解。线程、进程和守护进程各有优缺点,适用于不同的场景。在实际应用中,我们需要根据任务的特点选择合适的并发方式,并结合优化技巧来提高程序的性能。

未来,随着计算机硬件的不断发展和Python语言的不断完善,并发编程将在更多领域得到应用。例如,人工智能、机器学习等领域对计算资源的需求越来越高,并发编程可以帮助我们更高效地利用这些资源。同时,异步编程、分布式计算等技术也将不断发展,为并发编程带来更多的可能性。

希望本文能够帮助你更好地掌握Python并发编程的技巧,在实际项目中发挥更大的作用。如果你有任何疑问或建议,欢迎留言讨论。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样统计,通过模拟系统元件的故障修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值