Python多线程


《Python3从入门到实战》及大拿、莫烦老师的Python笔记

一、threading模块

threading模块是一个高层多线程模块,它依赖底层的_thread模块,可以通过threading模块提供的接口编写多线程程序,threading模块的Thread类用于表示一个线程

threading相关函数

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

Thread类提供了以下几种方法

run():用于表示线程活动的方法
start():启动线程活动的方法
join([time]):等待至线程终止的方法,对一个线程对象x,调用x.join()方法会阻塞调用线程,直至线程x因正常退出,抛出未处理的异常终止,或超过等待时间为止.
isAlive():返回线程是否活动
getName():返回线程名
setName():设置线程名

创建一个线程有两种方法
1:直接创建一个Thread对象,并给它的构造函数传递一个可调用的对象(callable object)

2:从Thread类定义一个派生类,并重载其构造函数__init__()和__run__()方法,然后创建一个该派生线程类的对象并传递一个可调用的对象

Thread类的构造函数规范是:threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
其中target就是线程__run__()方法调用的可调用对象,即线程的执行代码,如果不提供,则系统会提供一个默认的名字,参数args和kwargs分别的tuple和dict类型的(可变)参数,即传递给target对象的参数.
通过Thread类对象的start()方法可以启动一个线程,Thread的start()方法会调用Thread的__run__()方法执行构造函数的参数target指向的可调用的对象,同时将参数args和kwargs传递给这个target对象

使用start方法启动线程

#下面的主线程中创建了5个对象,每个线程的构造函数中接受一个可调用对象,即函数worker(args),在该构造函数中,还传递了一个tuple对象(i,)作为函数worker()的参数args
import threading 
def worker(args):
    print("I am a Worker with params{}".format(args))
    print("This is the main thread!\n")
    
threads = []
for i in range(5):
    t = threading.Thread(target=worker,args=(i,))
    threads.append(t)
    t.start()

为线程命名


#为线程命名:Thread的构造函数有一个参数name,它是用来为线程命名的,如果没有提供这个参数,则系统在创建线程对象时会自动生成一个名字,可以通过这个参数为线程命名,以区分和跟踪
import threading
import time

def thread_A():
    print(threading.current_thread().getName(),'Starting')
    time.sleep(2)
    print(threading.current_thread().getName(),'Exiting')
    
def thread_B():
    print(threading.current_thread().getName(),'Starting')
    time.sleep(3)
    print(threading.current_thread().getName(),'Exiting')
    
    
t = threading.Thread(name="thread_A",target=thread_A)
w = threading.Thread(name="thread_B",target=thread_B) 
w2 = threading.Thread(target=thread_A)#使用默认名字
w.start()
w2.start()
t.start()


二、守护线程与非守护线程

守护线程和非守护线程:

一个线程可以被设置为是否为daemen(守护线程),Thread的构造函数的daemon参数默认是None,默认情况下创建的是非守护线程

一个程序只有一个主线程,即Python程序的初始线程,主线程必须等待这些非守护线程都执行完后才能退出,而守护线程不能阻塞主线程

即主线程不用等待守护线程执行完就可以退出,守护线程主要用于不需要用户交互的后台服务或那些即是突然死掉也不造成数据破坏的线程

可以在创建线程时设置参数daemon-Ture或创建一个线程后调用set_daemon(Ture)方法,将该线程设置为守护线程

守护线程案例是否有效跟环境有关系

import threading
import time
def non_daemon():
    print('non_daemon Starting')
    time.sleep(3)
    print('non_daemon Exiting')
    
def daemon():
    print("daemon Starting")
    time.sleep(10)
    print('daemon Exiting')
    print('main Starting')
    
if  __name__ == '__main__':
    t = threading.Thread(name='Non-daemon Thread',target=non_daemon)
    
    d = threading.Thread(name='daemon Thread',target=daemon)
    d.start()
    t.start()
    print('main Exiting')

join的使用

#join方法:如果希望主线程等待守护线程结束,则可在主线程中通过守护线程对象调用join方法,等待守护线程结束.在默认状态下,调用join方法会无限期地阻塞调用线程等待线程结束,也可以给join设置一个时间,join里面填秒数,当超过这个等待时间后,该主线程就不再等待
import threading,time

def non_daemon():
    print("非守护线程开始")
    print("非守护线程结束")
    
def daemon():
    print("守护线程开始")
    time.sleep(10)
    print("守护线程结束")
    
t = threading.Thread(name='F',target=non_daemon)
d = threading.Thread(name='S',target=daemon)
print('主线程开始')
d.start()
t.start()
d.join()
print("守护线程活跃状态:{}".format(d.isAlive()))
print('主线程结束')

枚举所有线程

'''
枚举所有线程:
可以通过threading模块的enumerate()方法枚举所有活动的线程(包括主线程)
'''
import random,threading,time

def worker():
    r = random.randint(1,6)/10
    time.sleep(r)

    
for i in range(3):
    t = threading.Thread(target=worker,name=str(i),daemon=True)
    t.start()
    
for i in threading.enumerate():
    print(i.getName())

三、定义一个派生类

也可以从Thread线程类定义一个派生类,然后创建这个派生类的对象,派生类需要重载__init__()方法和__run__()方法

import threading

class DerivedThread(threading.Thread):
    def __init__(self,args):
        super().__init__()
        self.args = args
        
    def run(self):
        print('子线程的参数{}'.format(self.args))
        
DerivedThread(1,).start()
print('退出主线程')
#派生类的构造函数的参数可以和Thread的构造函数的参数一样
import threading
class DerivedThread(threading.Thread):
    def __init__(self,group=None,target=None,name=None,args=(),kwargs=None,*,daemon=None):
        super().__init__(group=None,target=target,name=name,daemon=daemon)
        self.args = args
        self.kwargs = kwargs
    
    def run(self):
        print("子线程的参数{}--->{}".format(self.args,self.kwargs))
        
DerivedThread(1,kwargs={'name':'you'}).start()
DerivedThread(2,kwargs={'name':'me'}).start()
DerivedThread(3,kwargs={'name':'she'}).start()

Timer线程

#Timer线程:是Thread类的一个派生类,它通过给构造函数传递一个延迟参数,可以创建一个延迟一定时间再启动的线程,在延迟期间内,可以通过调用函数cancel()取消这个线程的执行
import threading,time

def f():
    print("f")
def q():
    print("q")
    
t1 = threading.Timer(3,f)
t1.setName('t1')
t2 = threading.Timer(5,q)
t2.setName('t2')

t1.start()
t2.start()
t2.cancel()

四、锁

可能由于全局解释器锁的原因,这里看不出什么区别

Lock锁和RLock重入锁:可以通过调用Lock对象或RLock对象的acqurie()方法和release()方法来获得和释放一个互斥锁对象,以保证每次只有一个线程能够访问共享对象


import threading,time
count = 0
lock = threading.Lock() #创建一个互斥锁对象

class CountThread(threading.Thread):
    def run(self):
        global count
        time.sleep(1)
        if lock.acquire():#lock.acquire()获得互斥锁,参数timeout设置最多等待时间
            count += 1
            print("{}------>{}".format(self.name,count))
            lock.release()
        
for i in range(5):
    t = CountThread(name = 'Thread' + str(i))
    t.start()
import threading,time,random
count = 0
lock = threading.Lock() #创建一个互斥锁对象
class CountThread(threading.Thread):
    def run(self):
        global count
        time.sleep(1)
        if lock.acquire():#lock.acquire()获得互斥锁
            print("{}已经获得锁".format(self.name))
            count += 1
            print("{}------>{}".format(self.name,count))
            lock.release()
            
for i in range(5):
    t = CountThread(name = 'Thread' + str(i))
    t.start()
    t1 = random.random()
    time.sleep(t1)
    print("{}睡眠了{},正在等待锁".format(t.getName(),t1))

一个线程只能请求一次Lock对象,如果一个线程有多处代码要请求一个Lock对象,那么除第一次外,其他请求都会失败,为了能够多次请求一个锁,可以用RLock对象代替,RLock对象内有一个计数器用于记录请求次数,相应第也要调用同样次数的release()才能完全释放RLock,使其他线程获得RLock的使用权

import threading
lock = threading.RLock()
print(lock.acquire())
print(lock.acquire())
'''
True
True
'''

使用with语句

#Lock和with语句是相容的,所以可以用with语句简化Lock对象的使用,以保证Lock对象自动获取和安全释放
import threading

def f_with(lock):
    with lock:
        print("使用with获取Lock对象")
def f_no_with(lock):
    lock.acquire()
    try:
        print("直接获取Lock对象")
    finally:
        lock.release() #必须使用release释放
        
    
lock = threading.Lock()
wt = threading.Thread(target=f_with,args=(lock,))
nwt = threading.Thread(target=f_no_with,args=(lock,))
wt.start()
nwt.start()
'''
使用with获取Lock对象
直接获取Lock对象
'''

五、线程通信

事件Event是一个简单的线程间通信的同步原子,一个Event对象内部维护一个标志,可以通过函数set(),函数clear()设置或清除一个线程,其他线程可以通过wait()方法等待这个Event被设置为Ture,即调用wait()方法的线程会一直阻塞,直到Event对象标志位True

import random,time,threading
event = threading.Event()
def waiter(event,n):
    for i in range(n):
        print("{}开始等待标志位True".format(i+1))
        event.wait() #阻塞直到表示为True
        print("等待完成时间为:{}".format(time.ctime()))
        event.clear()#清除标志
        
def setter(event,n):
    for i in range(n):
        time.sleep(random.randrange(2,5))
        event.set() #设置标志
        
n = random.randrange(2,5)
w = threading.Thread(target=waiter,args=(event,n))
s = threading.Thread(target=setter,args=(event,n))
w.start()
s.start()
s.join()
print("主线程结束")

生产者消费者模型

#条件(Condition):Condition对象是Event对象的高级版本,也用于进程间通信,并且可以用函数notify()通知其他线程的某个状态发生了变化,
#例如,通知某个资源以及具备(如网络已经连接,数据下载已经完成)其他线程在等待这个Conditon对象前必须首先获得acquire()这个对象(因此她也是一个锁,一个线程也应该通过realease()释放这个Condition对象,以便其他线程能够通过acquire获得Condition对象)
import threading,time,random
condition = threading.Condition()
products = []
def producer(products,nitems):
    for i in range(nitems):
        time.sleep(random.randrange(2,5))
        condition.acquire()
        num = random.randint(1,100)
        products.append(num)#将产品放入产品列表供消费者消费
        condition.notify()#通知消费者有产品
        print("生产力一个新产品{}--->{}".format(time.ctime(),num))
        condition.release()
        
def consumer(products,nitems):
    for i in range(nitems):
        time.sleep(random.randrange(2,5))
        condition.acquire()
        condition.wait() #阻塞,直到有一个产品可以消费
        print("消费一个产品{}--->{}".format(time.ctime(),products.pop()))
        condition.release()
        
threads = []
nloops = random.randrange(2,4)
nloops2 = random.randrange(2,4)
        
threads.append(threading.Thread(target=producer,args=(products,nloops,)))
threads.append(threading.Thread(target=consumer,args=(products,nloops2,)))

for thread in threads:
    thread.start()
for thread in threads: 
    thread.join()
print("退出主线程") 

信号灯Semaphore:管理一个内部计算器,acquire()会使计数器减少,而release()会使计数器增加,计数器永远不会小于0,当调用一个acquire()的线程计数器为0时,该线程就会阻塞,并等待其他线程调用release()释放该线程

import threading,time

#参数定义最多几个线程同时使用资源
semaphore = threading.Semaphore(3)

def func():
    if semaphore.acquire():#调用一次减少一次,不能小于0,等待重新释放才能用
        for i in range(5):
            print(threading.currentThread().getName()+'get semaphore')
        time.sleep(15)
        semaphore.release()
        print(threading.currentThread().getName()+'release semaphore')
        
for i in range(8):
    t = threading.Thread(target=func)
    t.start()
#下列代码用Semaphore模块解决生产者消费者问题
import random,time,threading
container = threading.BoundedSemaphore(3)#仓库最多存放三件商品(已经为3)
print(container)

def producer(nloops):
    for i in range(nloops):
        time.sleep(random.randrange(2,5))
        print(time.ctime(),end=": ")
        try:
            container.release()
            print("生产了一件产品")
        except ValueError:
            print("库存已满")
            
def consumer(nloops):
    for i in range(nloops):
        time.sleep(random.randrange(2,5))
        print(time.ctime(),": ")
    if container.acquire():
        print("消费一件商品")
    else:
        print("库存已空")
    
threads = []
nloops = random.randrange(2,4) 
nloops2 = random.randrange(2,4)
threads.append(threading.Thread(target=producer,args=(nloops,)))
threads.append(threading.Thread(target=consumer,args=(nloops2,)))
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()
print("退出主线程")

障碍Barrier


#障碍Barrier:每个调用Barrier对象的wait()方法的线程都会阻塞,只有等待所有调用该对象的wait()方法的线程都结束会后,
#这个Barrier条件才满足,这些线程就可以一起开始后续的执行过程。相当于急就是一起执行。
import threading,time,random

num = 2
barrier = threading.Barrier(num+1)

def worker():
    time.sleep(random.randrange(2,10))
    name = threading.current_thread().getName()
    barrier.wait()
    print("{}--->{}".format(name,time.ctime()))
    
for i in range(num):
    t = threading.Thread(name=str(i),target=worker)
    t.start()
    
barrier.wait()#主线程也等待
print("Exit!")
'''
1--->Tue Oct 27 16:12:17 2020Exit!0--->Tue Oct 27 16:12:17 2020
'''

六、queue

queue是线程安全类型,不加锁也不会引起任何问题

import threading
from queue import Queue
def job(l,q):
    for i in range(len(l)):
        l[i] = l[i] ** 2
    q.put(l[i])#放到q里面
    
def multithreading():
    q = Queue()
    threads = []
    data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
    for i in range(4):
        t = threading.Thread(target=job,args=(data[i],q))
        t.start()
        threads.append(t)    
    for thread in threads:
        thread.join()
    results = []
    for _ in range(4):
        results.append(q.get())#从q中按顺序拿出值
    print(results)
        
if __name__ == '__main__':
    multithreading()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值