多线程 及 分布式进程间的通信

#!/usr/bin/env python3

# -*- coding: utf-8 -*-

#!/usr/bin/env python3

#-*- coding:utf-8 -*-

#多线程

#多任务可以由多进程完成,也可以由一个进程内的多线程完成。

#进程是若干线程组成,一个进程至少有一个线程

#由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,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)

‘‘‘

thread MainThread is running...

thread LoopThread is running...

thread LoopThread >>>1

thread LoopThread >>>2

thread LoopThread >>>3

thread LoopThread >>>4

thread LoopThread >>>5

thread LoopThread ended

thread MainThread ended

‘‘‘

#由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回

#当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,

#如果不起名字Python就自动给线程命名为Thread-1,Thread-2...

#Lock

#多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何

#一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

#来看看多个线程同时操作一个变量怎么把内容给改乱了:

import time,threading

balance=0

def change_it(n):

global balance

balance=balance + n

balancd=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)

#我们定义了一个共享变量balance,初始值为0,并且启动两个线程,先加后减,理论上结果应该为0,但是,由于线程的调度是由操作系统决定的,当t1/t2交替

#执行时,只要循环次数足够多    ,balance的结果就不一定是0了。

#原因是因为高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:

#balance=balance+n

#也分两步:

#1.计算balance+n,存入临时变量中;

#2.将临时变量的值赋给balance。

#也可以看成是

#x=balance +n

#balance = x

#由于x是局部变量,两个线程各自都有自己的x,当代码正常执行时:

#t1和t2是交替运行的,如果操作系统以下面的顺序执行t1/t2:

#初始值 balance=0

‘‘‘

t1: x1=balance + 5

t2: x2=balance+8

t2:balance=x2

t1:balance=x1

t1:x1=balance-5

t1:balance=x1

t2:x2=balance-8

t2:balance=x2

结果 balance = -8

‘‘‘

#究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。

#如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行

#change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的

#冲突。创建一个锁就是通过threading.Lock()来实现:

balance=0

lock=threading.Lock()

def run_thread(n):

for i in range(10000):

#先要获取锁:

lock.acquire()

try:

#放心的改吧:

change_it(n)

finally:

#改完后要释放锁

lock.release()

#当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续执行代码,其他线程就继续等待直到获得锁为止。

#获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。

#锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程

#模式执行,效率就大大下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并在试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,

#既不能执行,也无法结束,只能靠操作系统强制终止。

#多核CPU

#一个死循环线程会100%占用一个CPU。如果有两个死循环,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。

#要想把N核CPU的核心全部跑满,就必须启动N个死循环线程。

#Python程序启动与CPU核心数量相同的N个线程,发现,在4核CPU上可以监控到CPU占用率仅有102%,也就是仅使用了一核。

#但是用C/C++/JAVA 来写相同的死循环,直接可以把全部核心跑满,4核就跑400%,8核就跑到800%,为什么Python不行呢?

#因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock ,任何Python线程执行前,必须先获得GIL锁,然后,每执行100

#条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100

#个线程跑在100核CPU上,也只能用到1个核。

#Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

#多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生。

#Python 解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。

#ThreadLocal

#在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的

#修改必须加锁。

#但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦。

#ThreadLocal对象可以解决这个问题

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内部会处理。

#可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等

#ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

#一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

#分布式进程

#在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。

#Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个

#进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。

#举个例子:如果我们已经有一个通过Queue通信的多进程程序在同一台机器上运行,现在,由于处理任务的进程任务繁重,希望把发送任务的进程和处理任务的进程

#分布到两台机器上。怎么用分布式进程实现?

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

#我们先看服务进程,服务进程负责启动Queue,把Queue注册到网络上,然后往Queue里面写入任务:

#task_master.py

import  random,time,queue

from multiprocessing.managers import BaseManager

#发送任务的队列:

task_queue=queue.Queue()

#接收结果的队列:

result_queue=queue.Queue()

#从BaseManager继承的QueueManager:

class QueueManager(BaseManager):

pass

#把两个Queue都注册到网络上,callable参数关联了Queue 对象:

QueueManager.register(‘get_task_queue‘,callable=lambda:task_queue)

QueueManager.register(‘get_result_queue‘,callable=lambda: result_queue)

#锁定端口5000,设置验证码‘abc’:

manager=QueueManager(address=(‘‘,5000),authkey=b‘abc‘)

#启动Queue:

manager.start()

#获得通过网络访问的Queue对象:

task=manager.get_task_queue()

result=manager.get_result_queue()

#放几个任务进去:

for i in range(10):

n=random.randint(0,10000)

print(‘Put task %d...‘ %n)

task.put(n)

#从result队列读取结果:

print(‘Try get results...‘)

for i in range(10):

r=result.get(timeout=10)

print(‘Result:%s‘%r)

#关闭:

manager.shutdown()

print(‘master exit.‘)

#请注意,当我们在一台机器上写多进程程序时,创建的Queue可以直接拿来用,但是,在分布式多进程环境下,添加任务到Queue不可以直接对原始的task_queue

#进行操作,那样就绕过了QueueManager的封装,必须通过manager.get_task_queue()获得的Queue接口添加。

#然后,在另一台机器上启动任务进程(本机上启动也可以):

#task_worker.py

import time,sys,queue

from multiprocessing.managers import BaseManager

#创建类似的QueueManager:

class QueueManager(BaseManager):

pass

#由于这个QueueManager只是从网络上获取Queue,所以注册时只提供名字:

QueueManager.register(‘get_task_queue‘)

QueueManager.register(‘get_result_queue‘)

#连接到服务器,也就是运行task_master.py 的机器:

server_addr=‘127.0.0.1‘

print(‘Connect to server %s ...‘ % server_addr)

#端口和验证码主要保持与task_master.py 设置的完全一致:

m=QueueManager(address=(server_addr,5000),authkey=b‘abc‘)

#从网络连接:

m.connect()

#获取Queue的对象:

task=m.get_task_queue()

result=m.get_result_queue()

#从task队列取任务,并把结果写入result队列:

for i in range(10):

try:

n=task.get(timeout=1)

print(‘run task %d * %d ...‘ % (n,n))

r=‘%d*%d=%d‘ %(n,n,n*n)

time.sleep(1)

result.put(r)

except Queue.Empty:

print(‘task queue is empty.‘)

#处理结束:

print(‘worker exit‘)

#任务进程要通过网络连接到服务进程,所以要指定服务进程的IP。

#现在,可以试试分布式进程的工作效果,先启动task_master.py服务进程:

#运行结果省略(因为没有服务器IP...)

#task_master.py 进程发送完任务后,开始等待result队列的结果。现在启动task_worker.py 进程:

#运行结果省略(因为没有服务器IP...)

#task_worker.py 进程结束,在task_master.py 进程中会继续打印出结果:

#运行结果省略(因为没有服务器IP...)

#这个简单的Master/Worker模型有什么用?其实这就是一个简单但真正的分布式计算,把代码稍加改造,启动多个worker,就可以把任务分布到几台甚至十几台机器上,比如

#把计算n*n的代码换成发送邮件,就实现了邮件队列的异步发送。

#Queue对象存储在哪?注意到task_worker.py中根本就没有创建Queue的代码,所以,Queue对象存储在task_master.py 进程中 :

#而Queue之所以能通过网络访问,就是通过QueueManager实现的。由于QueueManager管理的不止一个Queue,所以,要给每个Queue的网络调用接口起个名字,比如

#get_task_queue.

#authkey有什么用?这是为了保证两台机器正常通信,不被其他机器恶意干扰。如果task_worker.py 的 authkey和task_master.py的authkey不一致,肯定连接不上。

#Python 的分布式进程接口简单,封装良好,适合需要把繁重任务分布到多台机器的环境下。

#注意Queue的作用是用来传递任务和接收结果,每个任务的描述数量要尽量小。比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志

#文件存放的完整路径,由worker进程再去共享的磁盘上读取文件。

原文地址:https://www.cnblogs.com/Ting-light/p/9548071.html

时间: 2024-11-13 08:53:46

多线程 及 分布式进程间的通信的相关文章

Android进程间的通信之AIDL

Android服务被设计用来执行很多操作,比如说,可以执行运行时间长的耗时操作,比较耗时的网络操作,甚至是在一个单独进程中的永不会结束的操作.实现这些操作之一是通过Android接口定义语言(AIDL)来完成的.AIDL被设计用来执行进程间通信,另一种实现方式见博文Android进程间的通信之Messenger.本文我们将学习如何创建AIDL文件实现Android进程间通信.在正式学习之前,我们先澄清一些"事实". 关于Android Service 1.Android服务不是后台任务

Android 使用AIDL实现进程间的通信

在Android中,如果我们需要在不同进程间实现通信,就需要用到AIDL技术去完成. AIDL(android Interface Definition Language)是一种接口定义语言,编译器通过*.aidl文件的描述信息生成符合通信协议的Java代码,我们无需自己去写这段繁杂的代码,只需要在需要的时候调用即可,通过这种方式我们就可以完成进程间的通信工作.关于AIDL的编写规则我在这里就不多介绍了,读者可以到网上查找一下相关资料. 接下来,我就演示一个操作AIDL的最基本的流程. 首先,我

进程间的通信如何实现

进程间的通信如何实现 版权声明:本文为博主原创文章,未经博主允许不得转载.

linux 进程间的通信

现在linux使用的进程间通信方式:(1)管道(pipe)和有名管道(FIFO)(2)信号(signal)(3)消息队列(4)共享内存(5)信号量(6)套接字(socket) 为何进行进程间的通信:A.数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间B.共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到.C.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程).D.资源共享

Linux进程间的通信

一.管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: A. 管道是半双工的,数据只能向一个方向流动: B. 需要双工通信时,需要建立起两个管道: C. 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程): D. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中. 匿名管道的创建:该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义;因此,一

【转】使用AIDL实现进程间的通信之复杂类型传递

使用AIDL实现进程间的通信之复杂类型传递 首先要了解一下AIDL对Java类型的支持. 1.AIDL支持Java原始数据类型. 2.AIDL支持String和CharSequence. 3.AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句,即使位于同一个包中. 4.AIDL支持传递实现了android.os.Parcelable接口的复杂类型,同样在引用这些类型时也需要import语句.(Parcelable接口告诉Android运行时在封送(marsha

Linux进程间的通信方法

linux进程间的通信方法总结如下 通过fork函数把打开文件的描述符传递给子进程 通过wait得到子进程的终结信息 通过加锁的方式,实现几个进行共享读写某个文件 进行间通过信号通信,SIGUSR1和SIGUSR2实现用户定义功能 利用pipe进行通信 FIFO文件进行通信 mmap,几个进程映射到同一内存区 SYS IPC 消息队列,信号量(很少用) UNIX Domain Socket,常用

进程间的通信:管道

进程间的通信:管道 Linux中将命令联系到一起使用实际上就是把一个进程的输出通过管道传递给另一个进程的输入,这些都是shell封装好的,对标准输入和输出流进行了重新连接,使数据流从键盘输入经过两个程序最终输出到屏幕上.如下: cmd1|cmd2 进程管道 在两个程序之间传递数据最简单的方法就是使用popen()和pclose()了.原型如下: #include <stdio.h> FILE *popen(const char *command, const char *open_mode);

Linux/UNIX进程间的通信(1)

进程间的通信(1) 进程间的通信IPC(InterProcessCommunication )主要有以下不同形式: 半双工管道和FIFO:全双工管道和命名全双工管道:消息队列,信号量和共享存储:套接字和STREAMS 管道 pipe函数 当从一个进程连接到另一个进程时,我们使用术语管道.我们通常是把一个进程的输出通过管道连接到另一个进程的输入. 管道是由调用pipe函数创建的: #include<unistd.h> int pipe(intpipefd[2]); 经由参数pipefd返回两个文