Python的多进程与多线程

Python 多线程

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

  • 使用线程可以把占据长时间的程序中的任务放到后台去处理。
  • 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。 程序的运行速度可能加快。
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
  • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

线程模块

Python3 通过两个标准库 _thread 和 threading 提供对线程的支持。

_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]):等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

最近因为科研需要,我需要使用dtw(Dynamic Time Warping,动态时间归整)计算多个脑区的相关性,但是我有1611个人的数据,每个人需要计算点对点的连接6670个,我测了一下单个连接需要耗费2秒的时间。所以想起了使用python的多线程和多进程(恐怕也是杯水车薪,无妨,也可以不使用DTW)。这里谨做一下记录。如下所示,
我直接从 threading.Thread 继承创建了两个新的子类,并实例化后调用 start() 方法启动新线程,然后线程自己调用线程的 run() 方法

import threading
from time import ctime,sleep

def mddFc():
    path1 = r"F:\sklearn-ranforest\all\TimeSeries\MDD"
    filelist = os.listdir(path1)

    for i in range(832):
        file = filelist[i]
        m = loadmat(os.path.join(path1, file))
        matrix = m.get('ROISignals')
        ind = 0
        for j in range(0, 115):
            for k in range(j + 1, 116):
                X = matrix[:, j]
                Y = matrix[:, k]
                dis, paths = fastdtw(X, Y, dist=euclidean, radius=20)
                newX = []
                newY = []

                for path in paths:
                    fi = path[0]
                    se = path[1]
                    newX.append(X[fi])
                    newY.append(Y[se])

                cc = np.array([newX, newY])
                cc_pd = pd.DataFrame(cc.T, columns=['c1', 'c2'])
                cc_corr = cc_pd.corr(method='pearson')  # 相关系数矩阵
                val = cc_corr.iloc[0, 1]

                fc[ind][i] = dis
                fisherFc[ind][i] = atanh(val)
                ind = ind + 1

def ncFc():
    path2 = r"F:\sklearn-ranforest\all\TimeSeries\NC"
    filelist = os.listdir(path2)
    
    for i in range(779):
        file = filelist[i]
        m = loadmat(os.path.join(path2, file))
        matrix = m.get('ROISignals')
        ind = 0

        for j in range(0, 115):
            for k in range(j + 1, 116):
                X = matrix[:, j]
                Y = matrix[:, k]

                dis, paths = fastdtw(X, Y, dist=euclidean, radius=20)

                newX = []
                newY = []
                for path in paths:
                    fi = path[0]
                    se = path[1]
                    newX.append(X[fi])
                    newY.append(Y[se])

                cc = np.array([newX, newY])
                cc_pd = pd.DataFrame(cc.T, columns=['c1', 'c2'])
                cc_corr = cc_pd.corr(method='pearson')  # 相关系数矩阵
                val = cc_corr.iloc[0, 1]
                fc2[ind][i] = dis
                fisherFc2[ind][i] = atanh(val)
                ind = ind + 1

import threading
import time

class mddThread(threading.Thread):
    def __init__(self, threadID, name):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name

    def run(self):
        print("开始线程:" + self.name)
        mddFc()
        print("退出线程:" + self.name)



class ncThread(threading.Thread):
    def __init__(self, threadID, name):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name

    def run(self):
        print("开始线程:" + self.name)
        ncFc()
        print("退出线程:" + self.name)

# 创建新线程
thread1 = mddThread(1, "Thread-mdd")
thread2 = ncThread(2, "Thread-nc")
# 开启新线程
thread1.start()
thread2.start()

thread1.join()
thread2.join()
#等待两个线程执行完毕,主线程再执行这一行代码
print("运行结束")

大概十分钟计算了300个大脑之间的功能连接。

但是Python的并发机制是这样的,他有一个GIL锁,同一个进程里面只有一个线程拿到。(在Cpython解释器中)

GIL(Global Interpreter Lock)全局解释器锁

为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。

那么,我们改如何解决GIL锁的问题呢?

1.更换cpython为jpython(不建议)

2.使用多进程完成多线程的任务

3.在使用多线程可以使用其他语言去实现

GIL在python中的版本差异:

1、在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100时进行释放。(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval 来调整)。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。
2、在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。

多进程和多线程选型:

1、CPU密集型代码(各种计算),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好,因为线程的频繁切换,获取GIL锁,所以性能可能还不如单线程,这种情况使用多进程执行比较好。
2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

使用mulitprocess.process模块实现多进程

process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
方法介绍

  • p.start():启动进程,并调用该子进程的p.run()
  • p.run():进程启动时运行的方法,正是它取调用target指定的函数
  • p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁。
  • p.is_alive():如果p任然运行,返回True
  • p.join([timeout]):主线程等待p终止。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

属性介绍

  • p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
  • p.name:进程的名称
  • p.pid:进程的pid
  • p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功

在Windows中使用process模块的注意事项
在Windows操作系统中由于没有fork(Linux操作系统中创建进程的机制),在创建 子进程的是时候会自动import启动它的这个文件,而在import的时候又执行了整个文件。因此如果讲process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if name =='main’判断保护起来,import的时候,就不会递归运行了。

def mddFc():
    row = 6670
    col = 832
    fc = [[0.0] * col for i in range(row)]
    fisherFc = [[0.0] * col for i in range(row)]

    # 这里fc为dtw计算的欧氏距离
    fc = np.array(fc)
    # 这里是利用dtw扭曲的两个时间序列,重新计算皮尔孙,然后fisher
    fisherFc = np.array(fisherFc)


    path1 = r"F:\sklearn-ranforest\all\TimeSeries\MDD"
    filelist = os.listdir(path1)

    for i in range(832):
        file = filelist[i]
        m = loadmat(os.path.join(path1, file))
        matrix = m.get('ROISignals')
        ind = 0
        for j in range(0, 115):
            for k in range(j + 1, 116):
                X = matrix[:, j]
                Y = matrix[:, k]
                dis, paths = fastdtw(X, Y, dist=euclidean, radius=20)
                newX = []
                newY = []

                for path in paths:
                    fi = path[0]
                    se = path[1]
                    newX.append(X[fi])
                    newY.append(Y[se])

                cc = np.array([newX, newY])
                cc_pd = pd.DataFrame(cc.T, columns=['c1', 'c2'])
                cc_corr = cc_pd.corr(method='pearson')  # 相关系数矩阵
                val = cc_corr.iloc[0, 1]

                fc[ind][i] = dis
                fisherFc[ind][i] = atanh(val)
                ind = ind + 1

        printf("处理完第%d个病人" % (i + 1))

    savemat(r'F:\sklearn-ranforest\all\TimeSeries\original\dtwDisMdd.mat', {
    
    'dtwDis': fc})
    savemat(r'F:\sklearn-ranforest\all\TimeSeries\original\fisherDtwCorrMdd.mat', {
    
    'dtwFc': fisherFc})


def ncFc():
    row = 6670
    col = 779
    fc2 = [[0.0] * col for i in range(row)]
    fisherFc2 = [[0.0] * col for i in range(row)]
    fc2 = np.array(fc2)
    fisherFc2 = np.array(fisherFc2)

    path2 = r"F:\sklearn-ranforest\all\TimeSeries\NC"
    filelist = os.listdir(path2)

    for i in range(779):
        file = filelist[i]
        m = loadmat(os.path.join(path2, file))
        matrix = m.get('ROISignals')
        ind = 0

        for j in range(0, 115):
            for k in range(j + 1, 116):
                X = matrix[:, j]
                Y = matrix[:, k]

                dis, paths = fastdtw(X, Y, dist=euclidean, radius=20)

                newX = []
                newY = []

                for path in paths:
                    fi = path[0]
                    se = path[1]
                    newX.append(X[fi])
                    newY.append(Y[se])

                cc = np.array([newX, newY])
                cc_pd = pd.DataFrame(cc.T, columns=['c1', 'c2'])
                cc_corr = cc_pd.corr(method='pearson')  # 相关系数矩阵
                val = cc_corr.iloc[0, 1]

                fc2[ind][i] = dis
                fisherFc2[ind][i] = atanh(val)
                ind = ind + 1
        printf("处理完第%d个正常人"%(i+1))

    savemat(r'F:\sklearn-ranforest\all\TimeSeries\original\dtwDisNc.mat', {
    
    'dtwDis': fc2})
    savemat(r'F:\sklearn-ranforest\all\TimeSeries\original\fisherDtwCorrNc.mat', {
    
    'dtwFc': fisherFc2})


if __name__ == '__main__':

    #target表示调用对象,即子进程要执行的任务
    mddProcess = Process(target=mddFc())
    ncProcess = Process(target=ncFc())

    mddProcess.start()
    ncProcess.start()

    mddProcess.join()
    ncProcess.join()
    print('子进程执行完毕')

如果任务比较多,就使用进程池

进程池multiprocessing.pool

Pool常用参数说明如下

  • apply_async(func[, args[, kwds]]):使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
  • close():关闭Pool,使其不再接受新的任务;
  • terminate():不管任务是否完成,立即终止;
  • join():主进程阻塞,等待子进程的结束, 必须在close或terminate之后使用
from multiprocessing import Pool

def job():


if __name__ == '__main__':
	#指定进程池中的进程最多为5个
    pool = Pool(5)
    for i in range(1, 1000):
        pool.apply_async(job)
    pool.close()
    pool.join()
    print('子进程运行结束')

参考资料:https://www.cnblogs.com/banyanisdora/p/14313061.html
https://www.cnblogs.com/luyuze95/p/11289143.html
https://www.runoob.com/python3/python3-multithreading.html
https://www.cnblogs.com/Lin2396/p/11568300.html
https://www.cnblogs.com/blueberry-mint/p/13627325.html

猜你喜欢

转载自blog.csdn.net/qq_37774171/article/details/121467270