python的进程、线程与协程

文章内容参考自廖雪峰python、传智播客python教程等

进程

进程的创建

from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))
    print('Child process will start.')
    p.start()
    p.join()
    print('Child process end.')

说明:
1.multiprocessing 是跨平台模块
2.os.getpid()用于获取当前进程标识
3.join()方法需要等待子线程结束后再往下执行,可以理解为子线程“插队”到主线程前执行
在这里插入图片描述在这里插入图片描述
实例1:

from multiprocessing import Process
import os
from time import sleep


def run_proc(name, age, **kwargs):
    for i in range(100):
    	print(i)
        print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
    sleep(2)
    print(kwargs)


if __name__ == '__main__':
    print('父进程 %d.' % os.getpid())
    p = Process(target=run_proc, args=('test', 18), kwargs={"m": 20})
    print('子进程将要执行')
    p.start()
    sleep(1)
    # 进程提前结束
    p.terminate()
    p.join()
    print('子进程已结束')

实例2:

# coding=utf-8
from multiprocessing import Process
import time
import os


# 两个⼦进程将会调⽤的两个⽅法
def worker_1(interval):
    print("worker_1,⽗进程(%s),当前进程(%s)" % (os.getppid(), os.getpid()))
    t_start = time.time()
    time.sleep(interval)  # 程序将会被挂起interval秒
    t_end = time.time()
    print("worker_1,执⾏时间为'%0.2f'秒" % (t_end - t_start))


def worker_2(interval):
    print("worker_2,⽗进程(%s),当前进程(%s)" % (os.getppid(), os.getpid()))
    t_start = time.time()
    time.sleep(interval)
    t_end = time.time()
    print("worker_2,执⾏时间为'%0.2f'秒" % (t_end - t_start))


if __name__ == "__main__":
    # 输出当前程序的ID
    print("进程ID:%s" % os.getpid())
    # 创建两个进程对象,target指向这个进程对象要执⾏的对象名称,
    # args后⾯的元组中,是要传递给worker_1⽅法的参数,
    # 因为worker_1⽅法就⼀个interval参数,这⾥传递⼀个整数2给它,
    # 如果不指定name参数,默认的进程对象名称为Process-N,N为⼀个递增的整数
    p1 = Process(target=worker_1, args=(2,))
    p2 = Process(target=worker_2, name="dongGe", args=(1,))
    # 使⽤"进程对象名称.start()"来创建并执⾏⼀个⼦进程,
    # 这两个进程对象在start后,就会分别去执⾏worker_1和worker_2⽅法中的内容
    p1.start()
    p2.start()
    # 同时⽗进程仍然往下执⾏,如果p2进程还在执⾏,将会返回True
    print("p2.is_alive=%s" % p2.is_alive())
    # 输出p1和p2进程的别名和pid
    print("p1.name=%s" % p1.name)
    print("p1.pid=%s" % p1.pid)
    print("p2.name=%s" % p2.name)
    print("p2.pid=%s" % p2.pid)
    # join括号中不携带参数,表示⽗进程在这个位置要等待p1进程执⾏完成后,
    # 再继续执⾏下⾯的语句,⼀般⽤于进程间的数据同步,如果不写这⼀句,
    # 下⾯的is_alive判断将会是True,在shell(cmd)⾥⾯调⽤这个程序时
    # 可以完整的看到这个过程,⼤家可以尝试着将下⾯的这条语句改成p1.join(1),
    # 因为p2需要2秒以上才可能执⾏完成,⽗进程等待1秒很可能不能让p1完全执⾏完成,
    # 所以下⾯的print会输出True,即p1仍然在执⾏
    p1.join()
    print("p1.is_alive=%s" % p1.is_alive())

实例3:

from multiprocessing import Process
import time
import os


# 继承Process类
class ProcessClass(Process):
    # 因为Process类本身也有__init__⽅法,这个⼦类相当于重写了这个⽅法,
    # 但这样就会带来⼀个问题,我们并没有完全的初始化⼀个Process类,所以就不能使⽤从这个类继承的⼀些⽅法和属性,
    # 最好的⽅法就是将继承类本身传递给Process.__init__⽅法,完成这些初始化操作
    def __init__(self, interval):
        Process.__init__(self)
        self.interval = interval
    
    # 重写了Process类的run()⽅法
    def run(self):
        print("⼦进程(%s) 开始执⾏,⽗进程为(%s)" % (os.getpid(), os.getppid()))
        t1_start = time.time()
        time.sleep(self.interval)
        t1_stop = time.time()
        print("(%s)执⾏结束,耗时%0.2f秒" % (os.getpid(), t1_stop - t1_start))


if __name__ == "__main__":
    t_start = time.time()
    print("当前程序进程(%s)" % os.getpid())
    p1 = ProcessClass(2)
    # 对⼀个不包含target属性的Process类执⾏start()⽅法,就会运⾏这个类中的run()⽅法,所以这⾥会执⾏p1.run()
    p1.start()
    p1.join()
    t_stop = time.time()
    print("(%s)执⾏结束,耗时%0.2f" % (os.getpid(), t_stop - t_start))

进程池

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

from multiprocessing import Pool
import os, time, random


def worker(msg):
    t_start = time.time()
    print("%s开始执⾏,进程号为%d" % (msg, os.getpid()))
    # random.random()随机⽣成0~1之间的浮点数
    time.sleep(random.random() * 2)
    t_stop = time.time()
    print(msg, "执⾏完毕,耗时%0.2f" % (t_stop - t_start))


if __name__ == "__main__":
    po = Pool(3)  # 定义⼀个进程池,最⼤进程数3
    for i in range(0, 10):
        # Pool.apply_async(要调⽤的⽬标,(传递给⽬标的参数元祖,))
        # 每次循环将会⽤空闲出来的⼦进程去调⽤⽬标
        po.apply_async(worker, (i,))

    print("----start----")
    po.close()  # 关闭进程池,关闭后po不再接收新的请求
    po.join()  # 等待po中所有⼦进程执⾏完成,必须放在close语句之后
    print("-----end-----")

在这里插入图片描述

进程间通信:

queue的使用:

# coding=utf-8
from multiprocessing import Queue

q = Queue(3)  # 初始化⼀个Queue对象,最多可接收三条put消息
q.put("消息1")
q.put("消息2")
print(q.full())  # False
q.put("消息3")
print(q.full())  # True
# 因为消息列队已满下⾯的try都会抛出异常,第⼀个try会等待2秒后再抛出异常,第⼆个Try会⽴刻抛出异常
try:
    q.put("消息4", True, 2)
except:
    print("消息列队已满,现有消息数量:%s" % q.qsize())
try:
    q.put_nowait("消息4")
except:
    print("消息列队已满,现有消息数量:%s" % q.qsize())
# 推荐的⽅式,先判断消息列队是否已满,再写⼊
if not q.full():
    q.put_nowait("消息4")
# 读取消息时,先判断消息列队是否为空,再读取
if not q.empty():
    for i in range(q.qsize()):
        print(q.get_nowait())

在这里插入图片描述
在这里插入图片描述
我们以Queue为例,在⽗进程中创建两个⼦进程,⼀个往Queue⾥写数据,⼀个从Queue⾥读数据:

from multiprocessing import Process, Queue
import time, random


# 写数据进程执⾏的代码:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())


# 读数据进程执⾏的代码:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print("Get {0}".format(value))
            time.sleep(random.random())
        else:
            break


if __name__ == "__main__":
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    pw.start()
    pw.join()
    pr.start()
    pr.join()
    print("--end--")

子进程
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。

subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。

下面的例子演示了如何在Python代码中运行命令nslookup www.python.org,这和命令行直接运行的效果是一样的:

import subprocess

print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)

运行结果:

$ nslookup www.python.org
Server:        192.168.19.4
Address:    192.168.19.4#53

Non-authoritative answer:
www.python.org    canonical name = python.map.fastly.net.
Name:    python.map.fastly.net
Address: 199.27.79.223

Exit code: 0

如果子进程还需要输入,则可以通过communicate()方法输入:

import subprocess

print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)

上面的代码相当于在命令行执行命令nslookup,然后手动输入:

set q=mx
python.org
exit

运行结果如下:

$ nslookup
Server:        192.168.19.4
Address:    192.168.19.4#53

Non-authoritative answer:
python.org    mail exchanger = 50 mail.python.org.

Authoritative answers can be found from:
mail.python.org    internet address = 82.94.164.166
mail.python.org    has AAAA address 2001:888:2000:d::a6


Exit code: 0

线程

线程的创建

实例1

# coding=utf-8
import threading
import time


def saySorry():
    print("亲爱的,我错了,我能吃饭了吗?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=saySorry)
        t.start()  # 启动线程,即让线程开始

实例2

# coding=utf-8
import threading
from time import sleep, ctime


def sing():
    for i in range(3):
        print("正在唱歌...%d" % i)
        sleep(1)


def dance():
    for i in range(3):
        print("正在跳舞...%d" % i)
        sleep(1)


if __name__ == '__main__':
    print('---开始---:%s' % ctime())
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    # 屏蔽下面这行代码,试试看,程序是否会⽴⻢结束?
    sleep(4)
    print('---结束---:%s' % ctime())

线程数量、执行顺序、状态

查看线程数量

import threading
from time import sleep,ctime
 
def sing():
    for i in range(3):
        print("正在唱歌...%d"%i)
        sleep(1)
 
def dance():
    for i in range(3):
        print("正在跳舞...%d"%i)
        sleep(1)
 
if __name__ == '__main__':
    print('---开始---:%s'%ctime())
 
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
 
    t1.start()
    t2.start()
 
    while True:
        length = len(threading.enumerate())
        print('当前运行的线程数为:%d'%length)
        if length<=1:
            break
 
        sleep(0.5)

线程的执行顺序
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法

import threading
import time
 
class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
            print(msg)
 
 
if __name__ == '__main__':
    t = MyThread()
    t.start()

注意:
1.每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
2.当线程的run()方法结束时该线程完成。
3.无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
在这里插入图片描述
多线程共享全局变量

from threading import Thread
import time
 
g_num = 100
 
def work1():
    global g_num
    for i in range(3):
        g_num += 1
 
    print("----in work1, g_num is %d---"%g_num)
 
 
def work2():
    global g_num
    print("----in work2, g_num is %d---"%g_num)
 
 
print("---线程创建之前g_num is %d---"%g_num)
 
t1 = Thread(target=work1)
t1.start()
 
#延时一会,保证t1线程中的事情做完
time.sleep(1)
 
t2 = Thread(target=work2)
t2.start()

将列表当做实参

from threading import Thread
import time
 
def work1(nums):
    nums.append(44)
    print("----in work1---",nums)
 
 
def work2(nums):
    #延时一会,保证t1线程中的事情做完
    time.sleep(1)
    print("----in work2---",nums)
 
g_nums = [11,22,33]
 
t1 = Thread(target=work1, args=(g_nums,))
t1.start()
 
t2 = Thread(target=work2, args=(g_nums,))
t2.start()

线程的同步

当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁。
互斥锁为资源引⼊⼀个状态:锁定/⾮锁定。
threading模块中定义了Lock类,可以⽅便的处理锁定:

#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([blocking])
#释放
mutex.release()

其中,锁定⽅法acquire可以有⼀个blocking参数。
如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为⽌
(如果没有指定,那么默认为True)
如果设定blocking为False,则当前线程不会堵塞

import threading
import time

g_num = 0


def test1(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁

    print("---test1---g_num=%d" % g_num)


def test2(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁

    print("---test2---g_num=%d" % g_num)


# 创建一个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()

# 创建2个线程,让他们各自对g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()

p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()

# 等待计算完成
while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

对于非共享变量,不需要加锁

# coding=utf-8
import threading
import time


class MyThread(threading.Thread):
    # 重写 构造方法
    def __init__(self, num, sleepTime):
        threading.Thread.__init__(self)
        self.num = num
        self.sleepTime = sleepTime

    def run(self):
        self.num += 1
        time.sleep(self.sleepTime)
        print('线程(%s),num=%d' % (self.name, self.num))


if __name__ == '__main__':
    mutex = threading.Lock()
    t1 = MyThread(100, 5)
    t1.start()
    t2 = MyThread(200, 1)
    t2.start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值