Python的进程与线程

进程

#-----------------------------------多进程----------------------------------#
#Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。
#普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,
#因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),
#然后,分别在父进程和子进程内返回。
#有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,
#常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,
#就fork出子进程来处理新的http请求。
#-------------------------------multiprocessing------------------------------#
#multiprocessing模块就是跨平台版本的多进程模块。
#multiprocessing模块提供了一个Process类来代表一个进程对象
from multiprocessing import Process
import os
#子进程要执行的代码
def run_proc(name):
    print('Run Child Process %s (%s)...'  %(name,os.getpid()))
    print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))  #getppid()可以拿到父进程的ID

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.')
#Parent Process 17052.
#Child Process will start.
#Run Child Process test (23404)...
#I am child process (23404) and my parent is 17052.
#Child Process end.
#----------------------------Pool-启动大量子进程---------------------------#
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)  #同时跑的进程个数 ,默认等于CPU的核数
    for i in range(5):  #这里创建了5个进程,其中4个会同时跑完,最后一个会等前面的task执行完之后再执行
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()#对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了
    print('All subprocesses done.')
#--------------------------------子进程----------------------------------#
#很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
#subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
import subprocess

print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)
#运行的结果是在每一个子进程开启之后都输出以下值
#$ nslookup www.python.org
#服务器:  dec3000.xjtu.edu.cn
#Address:  202.117.0.20

#非权威应答:
#名称:    python.map.fastly.net
#Addresses:  2a04:4e42:6::223
#          151.101.24.223
#Aliases:  www.python.org

#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)
'''
#--------------------------------进程间通信----------------------------------#
#Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
#我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
from multiprocessing import Process,Queue
import os,time,random

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

#读数据进程执行的代码
def read(q):
    print('Process to read: %s' % os.getpid())
    while True:
        value=q.get(True)
        print('Get %s from queue.' % value)
if __name__=='__main__':
    #父进程创建Queue,并传给各个子进程
    q=Queue()
    pw=Process(target=write,args=(q,))
    pr=Process(target=read,args=(q,))
    #启动子进程pw,写入
    pw.start()
    #启动子进程pr,读出
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()
#Process to write:21384
#Put A to queue...
#Process to read: 26040
#Get A from queue.
#Put B to queue...
#Get B from queue.
#Put C to queue...
#Get C from queue.

线程

#线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,
#并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。
#Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。
#绝大多数情况下,我们只需要使用threading这个高级模块。
#--------------------------------------创建子线程--------------------------------------------#
#启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
import time,threading
#新线程执行的代码
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n=0
    while n<5:
        n=n+1
        print('thread %s >>>%s' % (threading.current_thread().name,n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')  #把子线程要执行的函数以及子线程的名称 传入
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

#由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,
#Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。
#主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。
#名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

#------------------------------------多线程的资源共享---------------------------------------#
#多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,
#而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,
#因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
import time, threading
# 假定这是你的银行存款:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:但是由于线程的调度是由操作系统决定的,
    #当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。
    #究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。
    #balance = balance + n可以看成:x = balance + n     balance = x
    global balance  #如果要对全局变量赋值,就要用global进行声明
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)  #输出可能不是0
#----------------------------------多线程的资源共享——锁-------------------------------------#
#我们必须确保一个线程在修改balance的时候,别的线程一定不能改。
#如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,
#我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。
#由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。
#创建一个锁就是通过threading.Lock()来实现:

import time, threading
# 假定这是你的银行存款:
balance = 0
lock=threading.Lock()

def change_it(n):
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        #先要获取锁
        lock.acquire()
        try:
            change_it(n) #可以放心的改了
        finally:
            lock.release() #使用完了要将锁释放

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)  #输出一定是0
#当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
#获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
#锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,
#坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。
#其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,
#导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

#-------------------------------------多核CPU----------------------------------------#
#用C++或者Java写包含与CPU核数相同个数的死循环的N个线程,那么直接就可以把全部的核跑满
#但是对于Pyhton,只能占用一个核
#因为:Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,
#任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。
#这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
#不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
import threading, multiprocessing

def loop():
    x = 0
    while True:
        x = x ^ 1
        print(x)

#for i in range(multiprocessing.cpu_count()):
#    t = threading.Thread(target=loop)
#    t.start()
#------------------多线程并发在Python中是一个美丽的梦^*^-------------------------#

#--------------------------比较单线程和多线程的用时-------------------------------#
import time, threading
print('本程序将进行比较,单线程和多线程的用时')
balance=0
lock = threading.Lock()

def change_it(n):
    global balance
    balance = balance + n
    balance = balance - n

def run_thread1(n):
    for i in range(2000000):
        lock.acquire()
        try:
            change_it(n)
        finally:
            lock.release()

def run_thread2(n):
    for i in range(1000000):
        lock.acquire()
        try:
            change_it(n)
        finally:
            lock.release()

balance=0
start1 = time.time()
td = threading.Thread(target=run_thread1, args=(5,))
td.start()
td.join()
end1 = time.time()
timeuse1=end1-start1
print('\nbalance最终结果:%d        单线程用时:%f 秒'%(balance,timeuse1))


balance=0
start2 = time.time()
t1 = threading.Thread(target=run_thread2, args=(5,))
t2 = threading.Thread(target=run_thread2, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
end2 = time.time()
timeuse2=end2-start2
print('\nbalance最终结果:%d        多线程用时:%f 秒'%(balance,timeuse2))


#balance最终结果:0        单线程用时:0.788590 秒
#balance最终结果:0        多线程用时:1.616212 秒
#多线程要进行锁的等待,用时较长

#--------------------------ThreadLocal-------------------------------#
#在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,
#因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
#但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦
#可以用一个全局dict存放所有的Student对象,然后以thread自身作为key获得线程对应的Student对象
'''
global_dict = {}

def std_thread(name):
    std = Student(name)
    # 把std放到全局变量global_dict中:
    global_dict[threading.current_thread()] = std
    do_task_1()
    do_task_2()

def do_task_1():
    # 不传入std,而是根据当前线程查找:
    std = global_dict[threading.current_thread()]
    ...

def do_task_2():
    # 任何函数都可以查找出当前线程的std变量:
    std = global_dict[threading.current_thread()]
    ...
'''
#ThreadLocal采用上述思路,帮助查找dict
import threading
#创建全局ThreadLocal对象
local_school=threading.local()
def process_student():
    #获取当前线程关联的student
    std=local_school.student
    print('Hello, %s (in %s)' % (std,threading.current_thread().name))
def process_thread(name):
    # 绑定ThreadLocal的student:
    local_school.student = name
    process_student()

t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
#Hello, Alice (in Thread-A)
#Hello, Bob (in Thread-B)

#全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。
#你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,
#可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。

分布式进程

#Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。
#一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。
#由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。
#举个例子:如果我们已经有一个通过Queue通信的多进程程序在同一台机器上运行,
#现在,由于处理任务的进程任务繁重,希望把发送任务的进程和处理任务的进程分布到两台机器上。怎么用分布式进程实现?

#原有的Queue可以继续使用,但是,通过managers模块把Queue通过网络暴露出去,就可以让其他机器的进程访问Queue了。

#----------------------------------------服务进程----------------------------------------#
#服务进程负责启动Queue,把Queue注册到网络上,然后往Queue里面写入任务
import random,time,queue,threading
from multiprocessing.managers import BaseManager
from multiprocessing import freeze_support#>>>>2.modify:add this if your system is windows
from task_worker import calcu#task_worker.py
#send mession queue
task_queue=queue.Queue()
#receive mession queue 
result_queue=queue.Queue()

#heir form Base...
class QueueManager(BaseManager):
    pass
#>>>>1.modify:replace lamda expression with function def task...
def task_q():
    return task_queue
def result_q():
    return result_queue

def test():
    #register two queue to network and connect  para callable to object Queue
    QueueManager.register('get_task_queue',callable=task_q)
    QueueManager.register('get_result_queue',callable=result_q)
    #binding port:5000 and set password 'abc'
    manager=QueueManager(address=('127.0.0.1',5000),authkey=b'abc')
    #launch queue
    manager.start()
    #get object queue from network
    task=manager.get_task_queue()
    result=manager.get_result_queue()
    #set mession
    for i in range(10):
        n=random.randint(0,10000)
        print('Put task %d...'%n)
        task.put(n)
    #get result from queue
    print('Try to get results...')
    for i in range(10):
        r=result.get(timeout=10)
        print('Result:%s'%r)
    #close
    manager.shutdown()
    print('master exit.')

if __name__=='__main__':
    freeze_support()#>>>>2.modify:add this if your system is windows
    t1=threading.Thread(target=test,name='master')
    t2=threading.Thread(target=calcu,name='worker')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('Done...')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值