多线程

1.概念

  1.1 进程:(有时也称为重量级进程),是一个执行中的程序。每个程序都有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。

  1.2 线程:线程是操作系统够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  1.3全局解释器锁: 尽管Python解释器中可以运行多个线程,但是在任意给定的时刻,只有一个线程会被解释器执行。这就是由全局解释器锁控制的(GIL)。

  多线程的环境中,Python虚拟机会按照如下的步骤执行(借鉴与Python核心编程):

    a.设置GIL

    b.切换一个线程进去

    c.执行如下操作之一:(1).指定数量的得字节码指令。(2).线程主动让出控制权

    d.把线程设置睡眠状态(切换出线程)

    e.解锁GIL

    f.重复上述步骤

  自我理解:任意时刻,一个进程中只会有一个线程被Python解释器调用,并且随机,排队中的线程获得下次GIL也是随机的。这样看来Python中的多线程是否多余了呢?其实不然,相对于计算密集型的线程可能不太能感受到多线程的效果,但IO密集型的线程就会有很好的体验。在Python中。这个可以用多进程,或者协程进行弥补,但是多进程又要考虑到进程之间的数据交流问题,下面谈!!!!

2.多线程(threading模块)

 2.1 Thread 的属性和方法

  Atttribute:

    name ;线程名

    ident: 线程的标识符  

    daemon: 布尔标志,表示这个线程是否为守护线程

  Method:

    start() 开始执行线程

    run() 定义线程功能的方法(通常在子类中应用被开发者重写)

    join(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout,否则会一直阻塞

    getName() 返回线程名 

    setName(name) 设定线程名

    setDaemon() 把线程的守护标志设定为布尔值TRUE (必须在线程开始start()之前调用)

2.2.两种创建方式  

  a.创建Thread实例,传给他一个函数

  

import threading
import time
 
def func(num): #定义每个线程要运行的函数
 
    print("running on number:%s" %num)
 
    time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = threading.func(target=sayhi,args=(1,)) #生成一个线程实例
    t2 = threading.func(target=sayhi,args=(2,)) #生成另一个线程实例
 
    t1.start() #启动线程
    t2.start() #启动另一个线程
 
    print(t1.getName()) #获取线程名
    print(t2.getName())

   b.派生Thread的子类,并创建子类的实例

 

import threading
import time
 
 
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
 
    def run(self):#定义每个线程要运行的函数
 
        print("running on number:%s" %self.num)
 
        time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

2.3 join() 与 setDaemon()

  setDaemon() :将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦 

  join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

3.同步锁(Lock)

 问题:多个线程都在操作一个共享资源,造成了资源破坏(也就是,当一个线程的数据为未处理结束,其他线程已经开始)

 不用join的原因:join会把整个线程给停住,失去了多线程的意义

import time
import threading

def addNum():
    global num #在每个线程中都获取这个全局变量
    # num-=1
    lock.acquire()
    temp=num
    print('--get num:',num )
    #time.sleep(0.1)
    num =temp-1 #对此公共变量进行-1操作
    lock.release()

num = 100  #设定一个共享变量
thread_list = []
lock=threading.Lock()#通过创建Lock对象获得锁

for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list: #等待所有线程执行完毕
    t.join()

print('final num:', num )

4.线程死锁和递归锁

  死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁

def dotA():
    lockA.acquire()
    lockB.acquire()
    '''
    数据操作    
    '''
    lockB.realse()
    lockA.realse()
def dotB():
    lockB.acquire()
    lockA.acquire()
    '''
    数据操作    
    '''
    lockA.realse()
    lockB.realse()
此时lockA与lockB会互相的等待对方,造成死锁

  解决方式:使用递归锁

  为了支持在同一线程多次请求同一资源,Python提供了"可重入锁",threading.RLock,Rlock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire(),直到一个线程的所有的acquire都被relase,其他线程才能获得资源;将lock全部用threading.Rlock

5.条件同步变量

   有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。

wait():条件不满足时调用,线程会释放锁并进入等待阻塞;
notify():条件创造后调用,通知等待池激活一个线程;
notifyAll():条件创造后调用,通知等待池激活所有线程。

  

import threading,time
from random import randint
class Producer(threading.Thread):
    def run(self):
        global L
        while True:
            val=randint(0,100)
            print('生产者',self.name,":Append"+str(val),L)
            if lock_con.acquire():
                L.append(val)
                lock_con.notify()
                lock_con.release()
            time.sleep(3)
class Consumer(threading.Thread):
    def run(self):
        global L
        while True:
                lock_con.acquire()
                if len(L)==0:
                    lock_con.wait()
                print('消费者',self.name,":Delete"+str(L[0]),L)
                del L[0]
                lock_con.release()
                time.sleep(0.25)

if __name__=="__main__":

    L=[]
    lock_con=threading.Condition()
    threads=[]
    for i in range(5):
        threads.append(Producer())
    threads.append(Consumer())
    for t in threads:
        t.start()
    for t in threads:
        t.join()

6.同步条件:

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

  

from threading import Event
from threading import Thread
import time

def boss(event):
    print('今天晚上加班!!!')
    event.isSet() or event.set()
    time.sleep(5)
    print("下班了!!!")
    event.isSet() or event.set()
def worker(event):
    event.wait()
    print("命苦呀!!!!")
    event.clear()
    event.wait()
    print('欧耶!!!!')
def main():
    event = Event()
    event.clear()
    threads = []
    for i in range(4):
        threads.append(Thread(target=worker,args=(event,)))
    threads.append(Thread(target=boss,args=(event,)))
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()
    print('all done')
if __name__ == '__main__':
    main()

7.队列(Queue)

创建一个“队列”对象
import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。  class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。             class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。   class queue.PriorityQueue(maxsize)

此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作

  

posted @ 2018-08-06 17:36  夜尽天已明  阅读(172)  评论(0编辑  收藏  举报