python并发学习之多线程

一、什么是线程

    线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。python中不同的线程实际上并没有同时运行:它们只是看起来像是同时运行的。由于GIL(Global Interpreter Lock),python一次只能运行一个Python线程。

二、调用线程的2中方式

    python中使用threading实现多线程,有2中方式可以实现多线程调用

    1、直接调用

         threading.Thread() 一般接收两个参数:

         线程函数名:要放置线程让其后台执行的函数,由我们自已定义,注意不要加();

         线程函数的参数:线程函数名所需的参数,以元组的形式传入。若不需要参数,可以不指定。

import threading
import time
from time import ctime,sleep


def f1(n):
    print('f1:',n)
    time.sleep(n)
    print(time.time())

def f2(n):
    print('f2:',n)
    time.sleep(n)
    print(time.time())

#生成2个线程实例
t1=threading.Thread(target=f1,args=(3,))
t2=threading.Thread(target=f2,args=(6,))

#启动线程
t1.start()
t2.start()
View Code

     2、类继承调用         

        首先,我们要自定义一个类,对于这个类有两点要求,
        1、必须继承 threading.Thread 这个父类;
        2、必须覆写 run 方法。
        这里的 run 方法,和我们上面线程函数的性质是一样的,可以写我们的业务逻辑程序。在 start() 后将会调用。

class MyThread(threading.Thread):
    def __init__(self,num):
        #调用父类的初始化函数,注意,super().__init__() 一定要写,而且要写在最前面
        super().__init__()  
        #threading.Thread.__init__(self)
        self.num=num
     
     #必须重写run函数,而且想要运行应该调用start方法,run函数定义每个线程要运行的函数
    def run(self):  
        print("running on number:%s" %self.num)
        time.sleep(1)

if __name__=='__main__':
    t1=MyThread(1)
    t2=MyThread(2)
    t1.start()
    t2.start()
View Code
三、守护线程(setDaemon)  & join

       守护线程:

       无论是进程还是线程,都是:守护xxx会等待主xxx完毕后被销毁,

      主进程与主线程在什么情况下才算运行完毕

      1.主进程在其代码结束后就已经算运行完毕了,(守护进程就在此时被回收)。主进程会一直等非守护的子进程都运行玩不后回收子进程的资源,(否则会产生僵尸进程),才会结束

      2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。主线程的结束意味着进程的结束,进程整体的资源都被回收,因而主线程必须在其余非守护线程都运行完毕后才能结束

      1、setDaemon

        setDaemon(True):

    1.将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。

    2.换句话说:开启,子线程不会挂起,主线程执行了,子线程及时没执行完也会中断

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

     代码实例

import  threading, time
"""
设置两个线程
"""

def func1():
    print('--非守护线程开始--',ctime())
    time.sleep(2)
    print('--非守护线程结束--',ctime())

def func2():
    print('--守护线程开始--',ctime())
    time.sleep(4)
    print('--守护线程结束--',ctime())

if __name__ == '__main__':
    t1 = threading.Thread(target=func1,args=())
    t2 = threading.Thread(target=func2,args=())
    t2.setDaemon(True)
    t1.start()
    t2.start()
    print('主线程结束>>>>>>>>')
View Code

    执行结果

    从上面例子知func1为非守护线程,func2设置为守护线程,当主线程结束后,守护线程没执行完也会直接结束

--非守护线程开始-- Mon Jun 17 15:18:40 2019
--守护线程开始-- Mon Jun 17 15:18:40 2019
--非守护线程结束-- Mon Jun 17 15:18:42 2019

    2、join():

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

    代码实例

import threading
from time import ctime,sleep
import time

def music(func):
    for i in range(2):
        print ("Begin listening to %s. %s" %(func,ctime()))
        sleep(4)
        print("end listening %s"%ctime())

def move(func):
    for i in range(2):
        print ("Begin watching at the %s! %s" %(func,ctime()))
        sleep(1)
        print('end watching %s'%ctime())

threads = []
t1 = threading.Thread(target=music,args=('七里香',))
threads.append(t1)
t2 = threading.Thread(target=move,args=('阿甘正传',))
threads.append(t2)

if __name__ == '__main__':

    for t in threads:
        #t.setDaemon(True)  #把子进程设置为守护线程,必须在start()之前设置
        t.start()
        t.join()
    #t.join()
    # t1.join()
    #t2.join()########考虑这三种join位置下的结果?
    print ("all over %s" %ctime())
View Code

    执行结果

线程t1,t2均使用了join()方法  ‘all over Mon Jun 17 15:27:28 2019’ 会在t1,t2执行完后才会打印
仅t1使用join方法时,‘all over Mon Jun 17 15:27:28 2019’,会在t1执行完后就打印,然后打印t2内容
在for循环完使用使用t.join()时,此时就是for 循环中最后一个线程使用了join(),即t2.join()
t1、t2均使用join()时 Begin listening to 七里香. Mon Jun
17 15:27:18 2019 end listening Mon Jun 17 15:27:22 2019 Begin listening to 七里香. Mon Jun 17 15:27:22 2019 end listening Mon Jun 17 15:27:26 2019 Begin watching at the 阿甘正传! Mon Jun 17 15:27:26 2019 end watching Mon Jun 17 15:27:27 2019 Begin watching at the 阿甘正传! Mon Jun 17 15:27:27 2019 end watching Mon Jun 17 15:27:28 2019 all over Mon Jun 17 15:27:28 2019

仅t2使用join()时 

  Begin listening to 七里香. Mon Jun 17 15:49:24 2019
  Begin watching at the 阿甘正传! Mon Jun 17 15:49:24 2019
  end watching Mon Jun 17 15:49:25 2019
  Begin watching at the 阿甘正传! Mon Jun 17 15:49:25 2019
  end watching Mon Jun 17 15:49:26 2019
  all over Mon Jun 17 15:49:26 2019
  end listening Mon Jun 17 15:49:28 2019
  Begin listening to 七里香. Mon Jun 17 15:49:28 2019
  end listening Mon Jun 17 15:49:32 2019

    threading 模块提供的其他方法

threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
run(): 用以表示线程活动的方法。
start():启动线程活动。
join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
四、同步锁(Lock)

     当一个数据有多个线程都可以对其进行修改的时候,任何一个线程改变它都会对其他线程造成影响,如果我们某一个线程在使用完之前,其他线程不能对其修改,就需要对这个线程增加一个线程锁。

有2点需要注意
1.分析Lock的同时一定要说明:线程抢的是GIL锁,拿到执行权限后才能拿到互斥锁Lock
2.使用join与加锁的区别:join是等待所有,即整体串行,而锁只是锁住一部分,即部分串行

python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

    不使用锁时

    输出的结果可能是89,86,91等等

    io密集型:当第一个线程开始start的时候,由于sleep了0.001秒,这0.001秒对于人而言很短,但是对于cpu而言,这0.001秒已经做了很多的事情了,在这里cpu做的事情就是或许已经start了100个线程,所以导致大多数的线程调用的count值还是100,即temp=100,只有少数的线程完成了count=temp-1的操作,所以输出的count结果不确定,可能是89,86,91等。

    当不sleep且使用num-=1时结果是正常的是因为因为动作太快(完成这个动作在切换的时间内)

import time
import threading


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

num = 100  #设定一个共享变量
thread_list = []
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 )
View Code

    使用锁可以做到线程安全,每次操作共享数据时线程都会先1获取锁然后进行操作,就可以保证同一时刻仅有一个线程在操作共享数据

import time
import threading


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

num = 100  #设定一个共享变量
thread_list = []
lock=threading.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 )
View Code
 五、死锁和递归锁

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[41m%s 拿到A锁\033[0m' %self.name)

        mutexB.acquire()
        print('\033[42m%s 拿到B锁\033[0m' %self.name)
        mutexB.release()

        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print('\033[43m%s 拿到B锁\033[0m' %self.name)
        time.sleep(2)

        mutexA.acquire()
        print('\033[44m%s 拿到A锁\033[0m' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''
View Code

    解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
六、条件变量同步(Condition)

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

    lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传入锁,对象自动创建一个RLock()。

wait([timeout]):线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。调用wait()会释放Lock,直至该线程被Notify()、NotifyAll()或者超时线程又重新获得Lock.

notify(n=1):通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。

notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程(这个一般用得少)

    代码实例  

import threading,time
from random import randint



'''
条件变量同步(Condition)
除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法,一般线程间存在数据共享时使用
'''
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() #条件不满足时调用,线程会释放锁并进入等待阻塞 收到notify()的通知时程序从lock_con.acquire()处继续向下执行
                print('消费者',self.name,":Delete"+str(L[0]),L)
                del L[0]
                lock_con.release()
                time.sleep(1)

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()
View Code
七、同步条件(Event)

    条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event=threading.Event():条件环境对象,初始值 为False;

event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。

    代码实例

'''
同步条件(Event),和条件变量同步差不多意思,只是少了锁功能,一般线程间不需要共享数据时使用
'''

import threading,time
class Boss(threading.Thread):
    def run(self):
        print("BOSS:今晚大家都要加班到22:00。")
        event.set()
        time.sleep(5)
        print("BOSS:<22:00>可以下班了。")
        event.set()
class Worker(threading.Thread):
    def run(self):
        event.wait()
        print("Worker:哎……命苦啊!")
        time.sleep(0.25)
        event.clear()
        event.wait()
        print("Worker:OhYeah!")
if __name__=="__main__":
    event=threading.Event()
    threads=[]
    for i in range(5):
        threads.append(Worker())
    threads.append(Boss())
    for t in threads:
        t.start()
    for t in threads:
        t.join()
View Code
八、信号量(Semaphore)

     信号量用来控制线程并发数的,semaphore是一个内置的计数器,每当调用acquire()时,内置计数器-1,每当调用release()时,内置计数器+1,计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。一般BoundedSemaphore。

     代码实例

'''
信号量Semaphore控制线程并发数
'''
import threading,time
class myThread(threading.Thread):
    def __init__(self, num):
        #super().__init__()  # 调用threading类的构造方法,python3的写法super().__init__()
        threading.Thread.__init__(self)
        self.num = num
    def run(self):

        #semaphore.acquire() #和if semaphore.acquire()效果一样
        if semaphore.acquire():
            print(self.name,'开始执行:',time.ctime())
            begin = time.time()
            time.sleep(self.num+1)
            end = time.time()
            t=end-begin
            print(self.name,'结束执行:',time.ctime(),'耗时:',t)
            semaphore.release()

if __name__=="__main__":

    #semaphore=threading.Semaphore(5)
    semaphore = threading.BoundedSemaphore(5)
    thrs=[]
    for i in range(10):
        #semaphore.acquire()
        t=myThread(i)
        thrs.append(t)
        t.start()
    for i in  thrs:
        i.join()
    print('结束>>>>>>>>')
View Code
九、多线程利器(queue)
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异常。

    代码实例

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



class Production(threading.Thread):
    def run(self):
        while True:
            r = randint(0, 100)
            q.put(r)
            print("生产出来%s号包子" % r)
            sleep(1)

class Proces(threading.Thread):
    def run(self):
        while True:
            re=q.get()
            print("吃掉%s号包子" % re)




if __name__=="__main__":
    q=queue.Queue(10)
    threads=[Production(),Production(),Production(),Proces()]
    for i in threads:
View Code

转载于:https://www.cnblogs.com/quanloveshui/p/11024063.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值