一、基础概念

让步:线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行。

竞态条件:多个线程共同访问一片数据,则由于数据访问的顺序不 一样,有可能导致数据结果的不一致的问题。

全局解释器锁(GIL):全局解释器锁(GIL)是一个互斥锁,它可以 阻止多个本地线程一次执行Python字节码。这个锁主要是因为CPython的内存管理不是线程安全的。

执行流程

      1、设置GIL

      2、切换到一个线程去执行

      3、运行

                 指定数量的字节码指令

                 线程主动让出控制(可以调用time.sleep(0))

      6、把线程设置完睡眠状态

      7、解锁GIL

      8、再次重复以上步骤

       对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个 I/O 调用之前被释放,以允许其它的线程在这个线程等待 I/O 的时候运行。如果某线程并未使用很多 I/O 操作,它会在自己的时间片内一直占用处理器(和 GIL)。也就是说,I/O 密集型的Python程序比计算密集型的程序更能充分利用多线程环境的好处。

线程的退出

      线程可以调用thread.exit()之类的退出函数,也可以使用Python退出进程的标准方法,如sys.exit()。但是不可以直接kill一个进程。

一、   Python的threading模块


Thread

表示一个线程的执行的类

Lock

互斥锁

Rlock

可重入锁,使用单线程可以再次获得已获得的锁。

Condition

条件变量能让一个线程停下来,等待其他线程满足了某个条件

Event

通用的条件变量。多线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活。

Semaphore

等待锁的线程

1、Thread类

run()

用以表示线程活动的方法

start()

启动线程活动

join([time])

等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止——正常退出或者抛出未处理

的异常-或者是可选的超时发生。

is_alive()

返回线程是否活动的

name()

设置/返回线程名

daemon()

返回/设置线程的 daemon 标志,一定要在调用 start()函数前设置

创建线程的三种方法:

    创建一个Thread的实例,传给它一个函数。

import threading
from time import sleep,time

loops = [4,2]

# 线程执行的逻辑函数
def loop(nloop,nsec):
    print('start loop %s at:%s'%(nloop,time()))
    sleep(nsec)
    print('loop %s done at %s'%(nloop,time()))

def main():
    print('starting at:',time())
    threads = []
    nloops  = range(len(loops))
    # 创建但不开始执行
    for i in nloops:
        t = threading.Thread(target=loop, args=(i, loops[i]))
        threads.append(t)
        print(threads)
    # 开始执行
    for i in nloops:
        threads[i].start()
    # 让主线程等待
    for i in nloops:
        threads[i].join()

    print('all Done at:',time())

if __name__ == '__main__':
    main()

    

创建一个Thread的实例,传给它一个可调用的类对象

from threading import Thread
from time import sleep,time
loops = [4,2]
class ThreadFunc():
    def __init__(self,func,args,name=''):
        self.name = name
        self.func = func
        self.args = args
    def __call__(self):
        #创建新线程的时候,thread对象会调用我们的threadfunc对象,
        #这时会用到一个特殊函数__call__()
        self.func(*self.args)
def loop(nloop,nsec):
    print('start loop %s at:%s'%(nloop,time()))
    sleep(nsec)
    print('loop %s done at %s'%(nloop,time()))
def main():
    print('starting at:',time())
    threads = []
    nloops  = range(len(loops))
    for i in nloops:
        #创建ThreadFunc实例化的对象,创建所有线程
        t = Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__))
        threads.append(t)
        print(ThreadFunc(loop, (i, loops[i]), loop.__name__))
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print('all Done at:',time())
if __name__ == '__main__':
    main()

    创建一个Thread 的实例,传递给它一个可调用的类对象

from threading import Thread
from time import sleep,time
loops = [4,2]
class MyThread(Thread):
    def __init__(self,func,args,name=""):
        super().__init__()
        self.name = name
        self.func = func
        self.args = args
    def getResult(self):
        return self.res
    def run(self):
        print('strating',self.name,'at:',time())
        self.res = self.func(*self.args)
        print(self.name,'finished at:',time())
def loop(nloop,nsec):
    print('start loop %s at:%s'%(nloop,time()))
    sleep(nsec)
    print('loop %s done at %s'%(nloop,time()))
def main():
    print('starting at:',time())
    threads = []
    nloops  = range(len(loops))
    for i in nloops:
        #创建ThreadFunc实例化的对象,创建所有线程
        t = MyThread(loop, (i, loops[i]), loop.__name__)
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print('all Done at:',time())
if __name__ == '__main__':
    main()
start loop 0 at:1528184826.7092037
start loop 1 at:1528184826.710204
loop 1 done at 1528184828.7103183
loop 0 done at 1528184830.7094326
all Done at: 1528184830.7094326



2、Lock&Rlock类

原语锁定是一个同步原语,状态是锁定或未锁定。两个方法acquire()release() 用于加锁和释放锁。RLock 可重入锁是一个类似于Lock对象的同步原语,但同一个线程可以多次调用。Lock不支持递归加锁,也就是说即便在同线程中,也必须等待锁释放。通常建议改 RLock,它会处理“owning thread”和“recursion level”状态,对于同线程的多次请求锁为,只累加计数器。每次调 release()将递减该计数器,直到0时释放锁,因此 acquire() 和 release() 必须要成对出现。

from time import sleep
from threading import current_thread,Thread,RLock

lock = RLock()
def show(i):
    with lock:
        print(current_thread().name,i)
        sleep(0.1)
def tes():
    with lock:
        for i in range(3):
            show(i)
for i in range(20):
    Thread(target=tes).start()
print(lock)

    

Thread-1 0
<locked _thread.RLock object owner=5180 count=1 at 0x0000000001D705D0>
Thread-1 1
Thread-1 2
Thread-2 0
Thread-2 1
Thread-2 2
Thread-3 0
Thread-3 1
Thread-3 2
Thread-4 0
Thread-4 1
Thread-4 2
Thread-5 0
Thread-5 1
Thread-5 2

    with的主要两个作用是:第一,代替实现了try、excepte、finally的功能,使其代码简洁了很多。第二,自动释放其拥有的资源,此资源包括打开的文件获得的锁申请的内存等等。

3、Event

    事件用于在线程间通信。一个线程发出一个信号,其他一个或多个线程等待。Event通过一个内部标记来协调多线程运行。方法wait()阻塞线程执行,直到标记为Ture。 set() 将标记设为 True,clear() 更改标记为 False。isSet() 用于判断标记状态。

from threading import Event,Thread
def t_event():
    e = Event()
    return e
e = t_event()
def t():
    for i in range(5):
        print('start wait')
        e.wait()
        e.clear()
        print(i)

Thread(target = t).start()

4、Condition

    当需要线程关注特定的状态变化或事件的发生时使用这个锁定。可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。

acquire([timeout])/release():

调用关联的锁的相应方法

wait([timeout])

调用这个方法将使线程进入Condition的等待池等待通知并释放锁

使用前线程必须已获得锁定,否则将抛出异常。

notify()

调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

notifyAll()

调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

from threading import Condition,current_thread,Thread
from time import sleep
con = Condition()

def tc1():
    with con:
        for i in range(5):
            print(current_thread().name,i)
            sleep(0.3)
            if i == 3:
                con.wait()                #让出锁
def tc2():
    with con:
        for i in range(5):
            print(current_thread().name,i)
            sleep(0.1)
            if i == 3:
                con.notify()

Thread(target=tc1).start()
Thread(target=tc2).start()
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-2 0
Thread-2 1
Thread-2 2
Thread-2 3
Thread-2 4
Thread-1 4

    只有获取锁的线程才能调用wait() 和 notify(),因此必须在锁释放前调用。当wait()释放锁后,其他线程也可进入wait 状态。notifyAll() 激活所有等待线程,让它们去抢锁然后完成后续执行。

5、生产者-消费者问题和Queue模块


queue(size)

创建一个大小为size的Queue对象

qsize()

返回队列的大小(由于在返回的时候,队列可能会被其它线程修改,所以这个值是近似值

empty()

如果队列为空返回True,否则返回 False

full()

如果队列已满返回True,否则返回 False

put(item,block=0)

item放到队列中,block参数为True,相当于队伍排满了,就在旁边等着,腾出位置再插进去;block为False,相当于队伍排满了,没位置了,直接投诉

get(block=0)

从队列中取一个对象,如果给了block(不为 0),函数会一直阻塞到队列中有对象为止。block参数为True,相当于锅里没吃的了,就在旁边等着上新菜,上了新菜再直接端走;block为False,相当于锅里没货了,没耐心等新菜上锅,直接掀锅砸灶。

    Queue模块可以用来进行线程间通讯,让各个线程之间共享数据

from queue import Queue
from random import randint
from time import sleep,time
from threading import Thread

class MyThread(Thread):
    def __init__(self,func,args,name=""):
        super().__init__()
        self.name = name
        self.func = func
        self.args = args
    def getRusult(self):
        return self.res
    def run(self):
        print('strating',self.name,'at:',time())
        self.res = self.func(*self.args)
        print(self.name,'finished at:',time())
#write()和read()函数分别用来把对象放入队列和消耗队列中的一个对象。
#在这里我们使用字符串‘xxx’来表示队列中的对象。
def writeQ(queue):
    print('producing object for Q...')
    queue.put('xxx',1)
    print('size now',queue.qsize())
def readQ(queue):
    queue.get(1)
    print('consumed object from Q...',queue.qsize())
def writer(queue,loops):
    #writer()函数只做一件事,就是一次往队列中放入一个对象,等待一会
    #然后再做同样的事情。
    for i in range(loops):
        writeQ(queue)
        sleep(1)
def reader(queue,loops):
    #reader()函数只做一件事,就是一次从队列中取出一个对象,等待一会
    #然后再做同样的事情。
    for i in range(loops):
        readQ(queue)
        sleep(randint(2,5))
#设置有多少个线程要被运行
funcs = [writer,reader]
nfuncs = range(len(funcs))
def main():
    nloops = randint(10,20)
    q = Queue(32)
    threads = []

    for i in nfuncs:
        t = MyThread(funcs[i],(q,nloops),funcs[i].__name__)
        threads.append(t)
    for i in nfuncs:
        threads[i].start()
    for i in nfuncs:
        threads[i].join()
        print(threads[i].getRusult())
    print("all Done")
if __name__ == '__main__':
    main()
strating writer at: 1528185472.6541495
producing object for Q...
size now 1
strating reader at: 1528185472.6541495
consumed object from Q... 0
producing object for Q...
size now 1
producing object for Q...
size now 2
consumed object from Q... 1
producing object for Q...
size now 2
producing object for Q...
size now 3
consumed object from Q... 2
producing object for Q...
size now 3
producing object for Q...
size now 4
producing object for Q...
size now 5
consumed object from Q... 4
producing object for Q...
size now 5
producing object for Q...
size now 6
producing object for Q...
size now 7
consumed object from Q... 6
producing object for Q...
size now 7
producing object for Q...
size now 8
producing object for Q...
size now 9
consumed object from Q... 8
writer finished at: 1528185486.6749518
None
consumed object from Q... 7
consumed object from Q... 6
consumed object from Q... 5
consumed object from Q... 4
consumed object from Q... 3
consumed object from Q... 2
consumed object from Q... 1
consumed object from Q... 0
reader finished at: 1528185511.6713815
None
all Done

常见问题解答:

1、进程与线程。线程与进程的区别是什么?

    进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。

线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中, 共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。 http://www.ruanyifeng.com/blo…