我的编程空间,编程开发者的网络收藏夹
学习永远不晚

python3--进程同步(multiprocess.Lock, Semaphore, Event)

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

python3--进程同步(multiprocess.Lock, Semaphore, Event)

socket基于tcp协议的多进程聊天(异步)

server端代码

import socket
from multiprocessing import Process
def chat(conn):
    while True:  # 接收消息,打印消息
        msg = conn.recv(1024).decode('utf-8')
        print(msg)
        conn.send((msg + '_sb').encode('utf-8'))
    conn.close()


if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1', 9595))
    sk.listen()
    while True:  # 创建多进程
        conn, addr = sk.accept()
        Process(target=chat, args=[conn,]).start()
    sk.close()


client端代码

import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9595))

while True:
    inp = input('>>>').encode('utf-8')
    sk.send(inp)
    msg = sk.recv(1024).decode('utf-8')
    print(msg)
sk.close()

先运行server程序,在运行client程序,执行结果为

download.gif


锁 —— multiprocess.Lock

通过之前的学习,实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。

当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题


多进程抢占输出资源,代码如下

import os
import time
import random
from multiprocessing import Process


def work(n):
    print('{} : {} is running'.format(n, os.getpid()))
    time.sleep(random.random())
    print('{} : {} is done'.format(n, os.getpid()))


if __name__ == '__main__':
    for i in range(3):
        p = Process(target=work, args=(i, ))  
        p.start()

执行结果为:

0 : 2840 is running

1 : 13824 is running

2 : 16980 is running

1 : 13824 is done

0 : 2840 is done

2 : 16980 is done


上面的输出的结果是随机的,如果想有序的执行,就需要用到锁,代码如下:

由并发变成了串行,牺牲了运行效率,但避免了数据混乱

import os
import time
import random
from multiprocessing import Process
from multiprocessing import Lock

def work(n, lock):
    lock.acquire()  #给进程上锁
    print('{} : {} is running'.format(n, os.getpid()))
    time.sleep(random.random())
    print('{} : {} is done'.format(n, os.getpid()))
    lock.release()  #给进程下锁


if __name__ == '__main__':
    lock = Lock()
    for i in range(3):
        p = Process(target=work, args=(i, lock))
        p.start()

执行结果

0 : 16160 is running

0 : 16160 is done

1 : 12368 is running

1 : 12368 is done

2 : 13824 is running

2 : 13824 is done


锁的概念

blob.png


如果锁加在进程的开始和结束,那么它和join,实现的效果是一样的(同步)

join的效果,代码如下

import os
import time
import random
from multiprocessing import Process
def work(n):
    print('{} : {} is running'.format(n, os.getpid()))
    time.sleep(random.random())
    print('{} : {} is done'.format(n, os.getpid()))


if __name__ == '__main__':
    for i in range(3):
        p = Process(target=work, args=(i,))
        p.start()
        p.join()

执行结果:

0 : 10544 is running

0 : 10544 is done

1 : 8332 is running

1 : 8332 is done

2 : 11280 is running

2 : 11280 is done


总结:同步控制

只要用到了锁,锁之内的代码就变成同步的了

锁:控制一段代码,同一时间,只能被一个进程执行,数据更加安全


锁的应用场景(12306抢票的例子)

模拟数据库,创建一个文件ticket,内容如下:

{"count":3}  #计数(剩余多少张票) 要先创建这个文件

代码如下:

# 文件db的内容为:{"count":3}
# 注意一定要用双引号,不然json无法识别
# 并发运行,效率高,但竞争写同一文件,数据写入错乱
import json
import time
import random
from multiprocessing import Process,Lock

def check_ticket(i):
    with open('ticket') as f:
        ticket_count = json.load(f)
    print('person{}查询当前余票 : '.format(i), ticket_count['count'])

def buy_ticket(i, lock):
    check_ticket(i)  # 调用查询余票函数
    lock.acquire()  # 锁进程
    with open('ticket') as f:
        ticket_count = json.load(f)
    time.sleep(random.random())  # 模拟读取延迟
    if ticket_count['count'] > 0:
        print('person{}购票成功'.format(i))
        ticket_count['count'] -= 1
        with open('ticket', 'w') as f:
            json.dump(ticket_count, f)
    else:
        print('余票不足,person{}购票失败'.format(i))
    time.sleep(random.random())  # 模拟写入延迟
    lock.release()  # 解锁进程

if __name__ == '__main__':
    lock = Lock()  # 实例化锁这个类得到一个对象lock
    for i in range(5):
        Process(target=buy_ticket, args=(i, lock,)).start()

执行结果

download.gif

#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但
牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理

#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是
mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往
往可以获得更好的可获展性。



信号量 —— multiprocess.Semaphore(了解)

互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。
假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。
实现:
信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,
acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器
这样的有限资源。
信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念

模拟ktv的房间数

blob.png

示例代码如下:在同一时间,最多进去4批人,acquire()是一个阻塞行为

import time
import random
from multiprocessing import Process, Semaphore

def ktv(i,sem):
    sem.acquire()  # 锁住进程
    print('person {} 进来唱歌了'.format(i))
    time.sleep(random.randint(1, 5))  # 模拟唱歌时间
    print('person {} 从ktv出去了'.format(i))
    sem.release()  # 解锁进程


if __name__ == '__main__':
    sem = Semaphore(4)  # 模拟只有4个房间
    for i in range(5):  # 模拟5批人需要唱歌
        Process(target=ktv, args=(i, sem)).start()

执行结果

download.gif


总结:信号量和锁有点类似,它们之间的区别,信号量,相当于计算器

信号量:

锁 + 计数器

.acquire() 计数器-1

计数器减为0 = 阻塞

.release() 计数器+1



事件 —— multiprocess.Event(了解)

事件介绍

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
    事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,
    如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True


事情的使用方法

示例1:

from multiprocessing import Event

# 创建一个事件的对象
e = Event()
print(e.is_set())  # 在事件的创始之初,状态为False

执行结果

False


例2:

from multiprocessing import Event

# 创建一个事件的对象
e = Event()
print(e.is_set())  # 在事件的创始之初,状态为False
e.wait()  # 阻塞,等待is_set()的值变成True,如果不为True,会一直等待
          # 后面的代码不会执行
print(1111)

执行结果

False  # 程序没有执行完毕,会一直阻塞


例3

from multiprocessing import Event

# 创建一个事件的对象
e = Event()
print(e.is_set())   # 打印is_set()的状态
                    # 在事件的创始之初,状态为False
e.set()  # 将is_set()的值改为True
e.wait()  # 阻塞,等待is_set()的值变成True,如果不为True,会一直等待
          # 后面的代码不会执行
print(e.is_set())  # 打印is_set()的状态

执行结果

False

True


例4:

from multiprocessing import Event

# 创建一个事件的对象
e = Event()
print(e.is_set())   # 打印is_set()的状态
                    # 在事件的创始之初,状态为False
e.set()  # 将is_set()的值改为True
e.wait()  # 阻塞,等待is_set()的值变成True,如果不为True,会一直等待
          # 后面的代码不会执行
print(e.is_set())  # 打印is_set()的状态
e.clear()  # 将is_set()的值改为False
print(e.is_set())  # 打印is_set()的值
e.wait()  # 阻塞,等待is_set()的值变成True
print(111)  # 代码不会被执行,上面被阻塞了

执行结果

False

True

False



模拟红绿灯

红绿灯:

    车 比喻是一个进程,wait()等红灯

       根据状态变化,wait遇到true信号,就非阻塞

                     遇到False,就阻塞

    交通灯 也是一个进程 红灯->False 绿灯->True


示例代码如下:

import time
import random
from multiprocessing import Process, Event
def car(i, e):  # 感知状态的变化
    if not e.is_set():  # 当前这个事件的状态如果是False
        print('car{}正在等待'.format(i))  # 这辆车正在等待通过路口
    e.wait()  # 阻塞,直到有一个e.set行为(True)  # 理解为等红灯
    print('car{}通过路口'.format(i))

def traffic_light(e):  # 修改事件的状态
    print('\033[1;31m红灯亮\033[0m')
    # 事件在创始之初的状态是False,相当于程序中的红灯

    time.sleep(2)  # 红灯亮2秒
    while True:
        if not e.is_set():  # False
            print('\033[1;32m绿灯亮\033[0m')
            e.set()  # 修改is_set()的值为 True
        elif e.is_set():
            print('\033[1;31m红灯亮\033[0m')
            e.clear()  # 修改is_set()的值为 False
        time.sleep(2)  # 等待2秒


if __name__ == '__main__':
    e = Event()
    Process(target=traffic_light, args=(e,)).start()
    for i in range(10):
        time.sleep(random.randrange(0, 5, 2))
        Process(target=car, args=(i, e)).start()


执行结果:

download.gif


进程间通信——队列和管道(multiprocess.Queue、multiprocess.Pipe)

进程间通信

IPC(Inter-Process Communication)


队列 

概念介绍

创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递

Queue([maxsize]) 

创建共享的进程队列。

参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。

底层队列使用管道和锁定实现


方法介绍

Queue([maxsize]) 
创建共享的进程队列。maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。
另外,还需要运行支持线程以便队列中的数据传输到底层管道中。 
Queue的实例q具有以下方法:

q.get( [ block [ ,timeout ] ] ) 
返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果
设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的
时间间隔内没有项目变为可用,将引发Queue.Empty异常。

q.get_nowait( ) 
同q.get(False)方法。

q.put(item [, block [,timeout ] ] ) 
将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,
将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发
Queue.Full异常。

q.qsize() 
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加
或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。


q.empty() 
如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和
使用结果之间,队列中可能已经加入新的项目。

q.full() 
如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。


其它方法了解

q.close() 
关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法
完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常
例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。

q.cancel_join_thread() 
不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。

q.join_thread() 
连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创
建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。


Queue与之前学的queue之间的区别

import queue

它能维护一个先进先出的次序 它不能进行IPC(进程间的通信)

from multiprocessing import Queue

它能维护一个先进先出的次序 也能进行IPC(进程间的通信)


进程间的通信

示例代码:

from multiprocessing import Queue, Process
# 能维护一个先进先出的次序,也能进行IPC

def haha(q):
    # 返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止
    print(q.get())


if __name__ == '__main__':
    q = Queue()
    p = Process(target=haha, args=(q, ))
    p.start()
    # 将[1, 2, 3]放入队列。如果队列已满,此方法将阻塞至有空间可用为止
    q.put([1, 2, 3])

执行结果

[1, 2, 3]


双向通信

import time
from multiprocessing import Queue, Process
# 能维护一个先进先出的次序,也能进行IPC

def haha(q):
    # 返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止
    print(q.get())
    # 将'你好'放入队列。如果队列已满,此方法将阻塞至有空间可用为止
    q.put('你好')


if __name__ == '__main__':
    q = Queue()
    p = Process(target=haha, args=(q, ))
    p.start()
    # 将'hi'放入队列。如果队列已满,此方法将阻塞至有空间可用为止
    q.put('hi')
    time.sleep(0.5)
    # 返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止
    print(q.get())

执行结果

hi

你好



生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。


为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。


什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。


基于队列实现生产者消费者模型

示例代码:

from multiprocessing import Process, Queue
import time, random, os


def consumer(q):
    while True:
        res = q.get()
        time.sleep(random.randint(1, 3))
        print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))


def producer(q):
    for i in range(10):
        time.sleep(random.randint(1, 3))
        res = '包子%s' % i
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' % (os.getpid(), res))


if __name__ == '__main__':
    q = Queue()
    # 生产者们:即厨师们
    p1 = Process(target=producer, args=(q,))

    # 消费者们:即吃货们
    c1 = Process(target=consumer, args=(q,))

    # 开始
    p1.start()
    c1.start()
    print('主')

执行结果:

download.gif

此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。

解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。


改良版--生产者消费模型

from multiprocessing import Process, Queue
import time, random, os


def consumer(q):
    while True:
        res = q.get()
        if res is None: break  # 收到结束信号则结束
        time.sleep(random.randint(1, 3))
        print('\033[1;32m%s 吃 %s\033[0m' % (os.getpid(), res))


def producer(q):
    for i in range(10):
        time.sleep(random.randint(1, 3))
        res = '包子%s' % i
        q.put(res)
        print('\033[1;31m%s 生产了 %s\033[0m' % (os.getpid(), res))
    q.put(None)  # 发送结束信号


if __name__ == '__main__':
    q = Queue()
    # 生产者们:即厨师们
    p1 = Process(target=producer, args=(q,))

    # 消费者们:即吃货们
    c1 = Process(target=consumer, args=(q,))

    # 开始
    p1.start()
    c1.start()
    print('主')

执行结果

download.gif

注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

python3--进程同步(multiprocess.Lock, Semaphore, Event)

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

python3--进程同步(multiprocess.Lock, Semaphore, Event)

socket基于tcp协议的多进程聊天(异步)server端代码importsocketfrommultiprocessingimportProcessdefchat(conn):whileTrue:#接收消息,打印消息msg=conn.r
2023-01-30

python中Event怎么实现线程间同步

这篇文章主要介绍“python中Event怎么实现线程间同步”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“python中Event怎么实现线程间同步”文章能帮助大家解决问题。前言:Event在pyt
2023-06-29

python进程间同步

阅读目录1. Process2. Lock3. Semaphore4. Event5. Queue6. Pipe7. Pool序. multiprocessingPython中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资
2023-01-31

Python3 串口两进程同时读写

通过两个进程分别读写串口,并把发送与接收到的内容记录在blog中,收到q时程序结束并退出import threading,timeimport serialimport stringclass SerThread: def __ini
2023-01-31

Python3 异步编程之进程与线程-1

Python3 异步编程之进程与线程-1一、了解进程间通信进程间通信进程线程线程 vs 进程IO模型并发 vs 并行异步 vs 同步二、多线程与多进程的用法计算密集型 vs I/O密集型GIL多线程多进程三、协程的好处与用法协程yieldy
2023-01-31

java中什么是进程同步与同步传输

进程同步就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。比如:早上起床后,先洗涮,然后才能吃饭,不能在洗涮没有完成时,就开始吃饭。按照这个定义,其实绝大多数函数都是同步调用(例如sin,isd
java中什么是进程同步与同步传输
2019-04-05

Python 中的进程、线程、协程、同步

进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO过程在什么时间发生?在刚刚结束的 PyCon2014 上海站,来自七牛云存储的 Python 高级工程师许智翔带来了关于 Python 的分享《Pyt
2023-01-31

Python实现进程同步和通信

引例:如之前创建多进程的例子# -*- coding:utf-8 -*-from multiprocessing import Process,Poolimport os,timedef run_proc(name): ##定
2023-01-31

Linux如何实现进程间同步

这篇文章主要为大家展示了“Linux如何实现进程间同步”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Linux如何实现进程间同步”这篇文章吧。##互斥量mutex我们已经知道了互斥量可以用于在线
2023-06-15

Python多进程,同步互斥,信号量,锁

进程补充进程间的信号信号量(信号灯)进程的同步互斥Event事件Lock 锁进程间的信号信号是唯一的异步通信方法一个进程向另一个进程发送一个信号来传递某种信息,接受者根据传递的信息来做相应的事$ kill -l查看系统信号说明$ kill
2023-01-30

Python多进程同步简单实现代码

本文讲述了Python多进程同步简单实现代码。分享给大家供大家参考,具体如下:#encoding=utf8 from multiprocessing import Process, Lock def func(lock, a):lock.a
2022-06-04

C++并发编程:如何进行线程同步与互斥?

线程同步在多线程并发访问共享资源时至关重要。c++++ 提供了互斥体、条件变量和原子操作来实现同步。互斥体确保一次仅一个线程访问资源;条件变量用于线程间通信;原子操作可确保单个操作不可中断执行。例如,使用互斥体同步对共享队列的访问,以防止数
C++并发编程:如何进行线程同步与互斥?
2024-05-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录