***

37651 ワード

マルチスレッドとスレッドの同期はネット上で多く話されていますが、ここで簡単にまとめます.
多くの場所でPythonのマルチスレッドは実際には「偽物」と言われています.なぜなら、Pythonの下層にはGILロックが実現されているからです.Global Interpreter Lockです.どんなPythonスレッドが実行される前に、まずGILロックを取得しなければなりません.それから、100バイトコードを実行するたびに、解釈器は自動的にGILロックを解放し、他のスレッドに実行する機会を与えます.このGILグローバルロックは実際にすべてのスレッドの実行コードをロックしているので,マルチスレッドはPythonで交互に実行するしかなく,100スレッドが100コアCPU上を走っても1コアしか使用できない.
Pythonではマルチスレッドを使用できますが、マルチコアの有効利用は期待できません.
0 x 01スレッド処理拡張クラス
Pythonで使用するスレッドには2つのモジュールがあり、低レベルのthreadとパッケージthreadの高度なthreadingは、threadingを推奨し、自分でthreadingを拡張することもできます.単純なモデルは次のとおりです.
#/usr/bin/env python
#coding:utf-8

__author__ = 'kikay'

import threading

class ThreadEx(threading.Thread):
    def __init__(self):
        super(ThreadEx,self).__init__()

    def run(self):
        pass

runメソッドを再ロードする必要があります.次に、具体的な例を示します.
# /usr/bin/env python
# coding:utf-8

__author__ = 'kikay'

import threading
import random
import time

#     
Num = 0

class ThreadEx(threading.Thread):
    def __init__(self,name):
        super(ThreadEx, self).__init__()
        self.__name=name

    def run(self):
        time.sleep(1)
        global Num
        print 'thread({0})is running...'.format(self.__name)
        #  10 
        for i in range(10):
            Num+=1

if __name__ == '__main__':
t = ThreadEx('t1')
t.start()
    print 'main is running ...'
    t.join()
    print 'Num is ',Num

サブスレッドはNumに対して10回の自己増加動作を行ったので,最終Numの値は10であった.
0 x 02スレッド同期
現在、複数のサブスレッドが同時にNumを自己増加操作していますが、Numの値が10以下であることを保証します.
# /usr/bin/env python
# coding:utf-8

__author__ = 'kikay'

import threading
import random
import time

#     
Num = 0

class ThreadEx(threading.Thread):
    def __init__(self,name):
        super(ThreadEx, self).__init__()
        self.__name=name

    def run(self):
        #time.sleep(1)
        global Num
        print 'thread({0})is running...'.format(self.__name)
        #  10 
        for i in range(10):
            if Num<10:
                time.sleep(0.1)
                Num=Num+1
                print "thread({0})'s Num is {1}".format(self.__name,Num)


if __name__ == '__main__':
    threadNum=10
    threads=[]

    for i in range(threadNum):
        t = ThreadEx('t{0}'.format(i))
        t.daemon=False
        threads.append(t)

    for i in range(threadNum):
        threads[i].start()

    print 'main is running ...'

    for i in range(threadNum):
        threads[i].join()

print 'Num is ',Num

実行結果は次のとおりです.
thread(t0)is running...
thread(t1)is running...
thread(t2)is running...
thread(t3)is running...
thread(t4)is running...
thread(t5)is running...
thread(t6)is running...
thread(t7)is running...
thread(t8)is running...
thread(t9)is running...
main is running ...
thread(t0)'s Num is 1
thread(t2)'s Num is 3thread(t1)'s Num is 3

thread(t3)'s Num is 4thread(t4)'s Num is 5

thread(t5)'s Num is 6
thread(t6)'s Num is 7
thread(t7)'s Num is 8
thread(t8)'s Num is 9
thread(t9)'s Num is 10
thread(t0)'s Num is 11
thread(t1)'s Num is 12
thread(t2)'s Num is 13
thread(t4)'s Num is 14
thread(t5)'s Num is 15
thread(t3)'s Num is 16
thread(t6)'s Num is 17
thread(t7)'s Num is 18
thread(t8)'s Num is 19
Num is  19

その結果,最終Numの値は19であり,10より大きいことが分かった.これは、スレッドの同期が行われていないためです.
if Num<10:
    time.sleep(0.1)
    Num=Num+1
    print "thread({0})'s Num is {1}".format(self.__name,Num)

上のコードは,ある時点で1つのサブスレッドがNum<10判定を行い,if判定下の文に入ったが,自己増分演算は行われず,この時間ギャップでは複数のサブスレッドもif下の文に入ってしまい,最終的にNumの値が10より大きくなった可能性がある.
Pythonには「ロック」されたオブジェクトが用意されており、簡単なスレッド同期を実現しています.RLockというモジュールを使うことをお勧めします(理由はhttp://blog.csdn.net/cnmilan/article/details/8849895)をクリックします.
# /usr/bin/env python
# coding:utf-8

__author__ = 'kikay'

import threading
import random
import time

#     
Num = 0
lock=threading.RLock()

class ThreadEx(threading.Thread):
    def __init__(self,name):
        super(ThreadEx, self).__init__()
        self.__name=name

    def run(self):
        #time.sleep(1)
        global Num
        global lock
        print 'thread({0})is running...'.format(self.__name)
        #  10 
        for i in range(10):
            lock.acquire()
            try:
                if Num<10:
                    time.sleep(0.1)
                    Num=Num+1
                    print "thread({0})'s Num is {1}".format(self.__name,Num)
            finally:
                lock.release()

if __name__ == '__main__':
    threadNum=10
    threads=[]

    for i in range(threadNum):
        t = ThreadEx('t{0}'.format(i))
        t.daemon=False
        threads.append(t)

    for i in range(threadNum):
        threads[i].start()

    print 'main is running ...'

    for i in range(threadNum):
        threads[i].join()

    print 'Num is ',Num

実行結果は次のとおりです.
thread(t0)is running...
thread(t1)is running...
thread(t2)is running...
thread(t3)is running...
thread(t4)is running...
thread(t5)is running...
thread(t6)is running...
thread(t7)is running...
thread(t8)is running...
thread(t9)is running...
main is running ...
thread(t0)'s Num is 1
thread(t1)'s Num is 2
thread(t2)'s Num is 3
thread(t3)'s Num is 4
thread(t4)'s Num is 5
thread(t5)'s Num is 6
thread(t6)'s Num is 7
thread(t7)'s Num is 8
thread(t8)'s Num is 9
thread(t9)'s Num is 10
Num is  10

0 x 03条件同期
上の「ロック」は基本的な同期を実現し、現在5つの自己増加スレッドと5つの自己減少スレッドが同時に実行されている場合(無限ループ)、Numの値が0未満、10未満であることを保証し、上のRLockを引き続き使用し、修正されたコードは以下の通りである.
# /usr/bin/env python
# coding:utf-8

__author__ = 'kikay'

import threading
import random
import time

#     
Num = 0
lock=threading.RLock()

#     
class ThreadAddEx(threading.Thread):
    def __init__(self,name):
        super(ThreadAddEx, self).__init__()
        self.__name=name
        self.__working=True

    def run(self):
        time.sleep(0.1)
        global Num
        global lock
        #print 'threadAdd({0})is running...'.format(self.__name)
        while self.__working:
            lock.acquire()
            try:
                if Num<10:
                    time.sleep(0.1)
                    Num=Num+1
                    print "threadAdd({0})'s Num is {1}".format(self.__name,Num)
            finally:
                lock.release()
    def stop(self):
        self.__working=False

#     
class ThreadSubEx(threading.Thread):
    def __init__(self,name):
        super(ThreadSubEx, self).__init__()
        self.__name=name
        self.__working=True

    def run(self):
        time.sleep(0.1)
        global Num
        global lock
        #print 'threadSub({0})is running...'.format(self.__name)
        while self.__working:
            lock.acquire()
            try:
                if Num>0:
                    time.sleep(0.1)
                    Num=Num-1
                    print "threadSub({0})'s Num is {1}".format(self.__name,Num)
            finally:
                lock.release()
    def stop(self):
        self.__working=False

if __name__ == '__main__':
    threadNum=5
    threads=[]

    for i in range(threadNum):
        t = ThreadAddEx('t{0}'.format(i))
        t.daemon=False
        threads.append(t)

    for i in range(threadNum):
        t = ThreadSubEx('t{0}'.format(i))
        t.daemon=False
        threads.append(t)

    for i in range(len(threads)):
        threads[i].start()

    print 'main is running ...'

    #  1s
    time.sleep(1)
    #  
    for i in range(len(threads)):
        threads[i].stop()

    for i in range(len(threads)):
        threads[i].join()

    print 'Num is ',Num

実行結果:
main is running ...
threadAdd(t0)'s Num is 1
threadAdd(t3)'s Num is 2
threadSub(t0)'s Num is 1
threadAdd(t4)'s Num is 2
threadAdd(t2)'s Num is 3
threadSub(t1)'s Num is 2
threadAdd(t1)'s Num is 3
threadSub(t3)'s Num is 2
threadSub(t2)'s Num is 1
threadSub(t4)'s Num is 0
threadAdd(t0)'s Num is 1
threadAdd(t3)'s Num is 2
threadSub(t0)'s Num is 1
threadAdd(t4)'s Num is 2
threadAdd(t2)'s Num is 3
threadSub(t1)'s Num is 2
threadAdd(t1)'s Num is 3
threadSub(t3)'s Num is 2
Num is  2

条件同期を実現した.しかし、上記の実装では、条件を満たすとサブスレッドは次のループに進む、10個の自己増加スレッド、1個の自己減少スレッドであれば、自己増加スレッドは大量に「無効」ループに陥り、lockを繰り返すことになると考えられる.acquireとlock.releaseでは、サブスレッドが条件に合致しない場合にwaitを行い、条件が合致した場合にサブスレッドを起動して実行を続行することはできませんか?PythonのConditionを利用して実現できます.
# /usr/bin/env python
# coding:utf-8

__author__ = 'kikay'

import threading
import random
import time

#     
Num = 0
# lock=threading.RLock()
con = threading.Condition()


#      
class ThreadAddEx(threading.Thread):
    def __init__(self, name):
        super(ThreadAddEx, self).__init__()
        self.__name = name
        self.__working = True

    def run(self):
        time.sleep(0.1)
        global Num
        # global lock
        global con
        # print 'threadAdd({0})is running...'.format(self.__name)
        while self.__working:
            con.acquire()
            try:
                time.sleep(0.1)
                if Num < 5:
                    Num = Num + 1
                    print "threadAdd({0})'s Num is {1}".format(self.__name, Num)
                    #     0,     
                    con.notify()
                #   
                else:
                    print 'threadAdd waiting ...'
                    con.wait()
            finally:
                con.release()

    def stop(self):
        self.__working = False


#      
class ThreadSubEx(threading.Thread):
    def __init__(self, name):
        super(ThreadSubEx, self).__init__()
        self.__name = name
        self.__working = True

    def run(self):
        time.sleep(0.1)
        global Num
        # global lock
        global con
        # print 'threadSub({0})is running...'.format(self.__name)
        while self.__working:
            con.acquire()
            try:
                time.sleep(0.1)
                if Num > 0:
                    Num = Num - 1
                    print "threadSub({0})'s Num is {1}".format(self.__name, Num)
                    #     10 ,      
                    con.notify()
                #   
                else:
                    print 'threadSub waiting ...'
                    con.wait()
            finally:
                con.release()
    def stop(self):
        self.__working = False


if __name__ == '__main__':
    threadNum = 10
    threads = []

    for i in range(threadNum):
        t = ThreadAddEx('t{0}'.format(i))
        t.daemon = False
        threads.append(t)

    for i in range(threadNum):
        t = ThreadSubEx('t{0}'.format(i))
        t.daemon = False
        threads.append(t)

    for i in range(len(threads)):
        threads[i].start()

    print 'main is running ...'

    #   5s
    time.sleep(5)
    #   
    for i in range(len(threads)):
        threads[i].stop()

    for i in range(len(threads)):
        threads[i].join()

    print 'Num is ', Num

実行結果:
main is running ...
threadAdd(t6)'s Num is 1
threadAdd(t4)'s Num is 2
threadAdd(t2)'s Num is 3
threadAdd(t0)'s Num is 4
threadAdd(t5)'s Num is 5
threadAdd waiting ...
threadAdd waiting ...
threadSub(t0)'s Num is 4
threadSub(t2)'s Num is 3
threadSub(t4)'s Num is 2
threadSub(t6)'s Num is 1
threadSub(t8)'s Num is 0
threadAdd(t9)'s Num is 1
threadSub(t1)'s Num is 0
threadSub waiting ...
threadSub waiting ...
threadSub waiting ...
threadSub waiting ...
threadAdd(t7)'s Num is 1
threadAdd(t8)'s Num is 2
threadAdd(t6)'s Num is 3
threadAdd(t4)'s Num is 4
threadAdd(t2)'s Num is 5
threadAdd waiting ...
threadAdd waiting ...
threadSub(t0)'s Num is 4
threadSub(t2)'s Num is 3
threadSub(t4)'s Num is 2
threadSub(t6)'s Num is 1
threadSub(t8)'s Num is 0
threadAdd(t9)'s Num is 1
threadSub(t1)'s Num is 0
threadAdd(t7)'s Num is 1
threadAdd(t8)'s Num is 2
threadAdd(t6)'s Num is 3
threadAdd(t4)'s Num is 4
threadAdd(t2)'s Num is 5
threadAdd waiting ...
threadSub(t0)'s Num is 4
threadSub(t2)'s Num is 3
threadAdd(t1)'s Num is 4
threadSub(t4)'s Num is 3
threadSub(t6)'s Num is 2
threadSub(t8)'s Num is 1
threadAdd(t9)'s Num is 2
threadSub(t1)'s Num is 1
threadSub(t3)'s Num is 0
threadSub waiting ...
threadAdd(t7)'s Num is 1
threadAdd(t8)'s Num is 2
threadSub(t7)'s Num is 1
threadAdd(t6)'s Num is 2
threadSub(t9)'s Num is 1
threadAdd(t4)'s Num is 2
threadAdd(t2)'s Num is 3
threadAdd(t0)'s Num is 4
threadSub(t0)'s Num is 3
threadSub(t2)'s Num is 2
threadAdd(t1)'s Num is 3
threadAdd(t5)'s Num is 4
threadSub(t4)'s Num is 3
threadSub(t6)'s Num is 2
threadSub(t8)'s Num is 1
threadAdd(t9)'s Num is 2
threadSub(t1)'s Num is 1
threadSub(t3)'s Num is 0
Num is  0

Conditionは、異なるスレッド間の同期の問題に適していることに注意してください.すなわち、notifyで「起動」したのは、本スレッドではなく他のスレッドです.
Queueモジュールを利用してFIFO同期を実現したり、他の同期を実現したりするモジュールなど、ネット上の例が多いので、ここではこれ以上説明しません.