20、多线程编程

一、线程基础

import threading

# 新建: 使用线程的第一步就是创建线程, 创建后的线程只是进入可执行的状态, 也就是Runnable
# Runnable: 进入此状态的线程还并未开始运行, 一旦CPU分配时间片给这个线程后, 该线程才正式的开始运行
# Running: 线程正式开始运行, 在运行过程中线程可能会进入阻塞的状态, 即Blocked
# Blocked: 在该状态下, 线程暂停运行, 解除阻塞后, 线程会进入Runnable状态, 等待CPU再次分配时间片给它
# 结束: 线程方法执行完毕或者因为异常终止返回
# 注意:sys.exit()只能结束当前线程




# 一、threading模块的三大函数:
# threading.current_thread()(或者threading.currentThread()),
# threading.active_count()(或者threading.activeCount()),
# threading.main_thread()

print(threading.current_thread())  # 当前线程 <_MainThread(MainThread, started 3788)>
print(threading.active_count())  # 1 活动线程总数
print(threading.main_thread())  # 主线程 <_MainThread(MainThread, started 3788)>

print(threading.currentThread().name)  # MainThread

# 二、创建线程
# 方法一
def fun(*a,b):
    print(threading.currentThread())  #  <Thread(tangjun, started 1328)>
    print(a, b)  # 1, 2, 3) 这是关键字参数


t = threading.Thread(target=fun, args=(1, 2, 3), kwargs={'b':'这是关键字参数'}, name='tangjun')  
# args 和 name可选,默认name是Thread-1,Thread-2...当有些参数必须用关键字传递值是就要用到:kwargs,其值是一个字典
t.start()  # 进入可执行的状态, 也就是Runnable,注意,并非马上执行,要看cpu是否给它机会

# 方法二
class MyThread(threading.Thread):
    #  注意,如果没有额外的参数,请不要重写父类构造方法,子类会自动调用父类的构造方法并传递参数
    def run(self) -> None:
        print(threading.currentThread())  # <MyThread(xiaodng, started 11432)>
        print(threading.activeCount())


t = MyThread(name='xiaodng')  # 参数是线程的名字,如果没有必要的话可以省略
t.start()  # 进入可执行的状态, 也就是Runnable,注意,并非马上执行,要看cpu是否给它机会

 

 

 

二、重要方法

1 thread.join(timeout)  让thread线程先执行,我等一等

暂停当前线程,等待thread线程结束(不设参数值,默认None)(或timeout秒后)再运行当前线程

 

 三、重要的类

线程锁:别的线程等一等,我先执行

threading.Lock()  threading.RLock()  threading.Condition()

1、threading.Lock()

look = threading.Lock() # 创建一个线程锁,注意,所有的线程要共有一把锁才能锁住资源,
.............................................
# 某个线程进入...
look.acquire() # 锁住资源
    ……运行可能会出现数据安全的代码
look.release() # 释放锁

2、threading.RLock()  在同一个线程内可以重复锁几次都不会锁死
import threading
rLock = threading.RLock()  #RLock对象  
rLock.acquire()  
rLock.acquire() #在同一线程内,程序不会堵塞。  
rLock.release()  # 释放锁,加几次锁就只能释放几次,不能多释放
rLock.release() 

3、threading.Condition 条件,高级锁,能高替代上面那两种锁,除此之个还提供了额外的功能:详情见后面

# 1、con = threading.Condition()  创建锁
# 2、 con.acquire()  # 给当前线程加锁,注意,可以反复加锁,一直持有锁。
# 3、con.release() 释放锁,加几次锁就只能释放几次,不能多释放
# 4、只有持有锁的线程(con.acquire())才可以wait(),notify(),notifyAll()
# 5、wait(timeout=None)等待,当前线程挂起(阻塞),同时自动释放锁(con.release()),可以设置超时timeout
# 6、notify(n=1)随机唤醒等待池中的n个线程,通知这n个线程去竟争锁acquire,最终只有一个线程内争到锁。参数默认值是1,可以传递一个自已喜欢的值。notify()可以替代notifyAll()
# 7、notifyAll()唤醒等待池中的所有线程,通知所有线程去竟争锁acquire。虽然全部都唤醒了,但是只有一个线程能够竟争到锁,其它线程在等待释放锁

# coding=utf-8
import threading, time
con = threading.Condition()
num = 0


# 生产者
class Producer(threading.Thread):
    def run(self):
        # 锁定线程
        global num
        con.acquire()  # 获得锁
        while True:
            print("开始添加!!!")
            num += 1
            print("火锅里面鱼丸个数:%s" % str(num))
            time.sleep(1)
            if num >= 5:
                print("火锅里面里面鱼丸数量已经到达5个,无法添加了!")
                # 唤醒等待的线程
                con.notify()  # 唤醒小伙伴开吃啦
                # 等待通知
                con.wait()  # 等待的同时会自动释放锁


# 消费者
class Consumers(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        con.acquire()
        global num
        while True:
            print("开始吃啦!!!")
            num -= 1
            print("火锅里面剩余鱼丸数量:%s" %str(num))
            time.sleep(2)
            if num <= 0:
                print("锅底没货了,赶紧加鱼丸吧!")
                con.notify()  # 唤醒其它1个线程
                # 等待通知
                con.wait()


p = Producer()
c = Consumers()
p.start()
c.start()
# coding=utf-8
# 线程 交替运行两个线程
import threading, time


class MyThread(threading.Thread):
    def run(self) -> None:
        global cn, count
        cn.acquire()
        while count < 10:
            count += 1
            print(threading.current_thread(), count)
            time.sleep(1)
            cn.notify()
            cn.wait()

        # 程序运行到此处时还有一个线程处于wait状态,所以需要notify,整个程序才会结束
        cn.notify()
        cn.release()
        

cn = threading.Condition()
count = 0
if __name__ == "__main__":
    mt1 = MyThread()
    mt2 = MyThread()
    mt1.start()
    mt2.start()

 

三、守护线程


当主线程结束后守护线程会立即结束
import threading
import time


def run(n):
  # 守护进程在执行下面的代码的时候,完全凭“运气”,也许能执行一条语句,也许一条也不能执行,因为主线程结束后守护线程就会被立即结束
print("task", n) time.sleep(1) # 此时子线程停1s print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) # 把子进程设置为守护线程,必须在start()之前设置 t.start() # 进入可执行的状态, 也就是Runnable,注意,并非马上执行,要看cpu是否给它机会 print("end")

 四,threading.Event

需要注意的是:event.set() 以后,event.wait()就不阻塞,所有的线程都不阻塞,event.clear()以后,将阻塞所有线程的event.wait()方法 。

 

# Event,与Condition的区别:Event没有锁,不存在竟争锁,多个线程可以同时运行(由cup随机调试),Condition有锁,多个线程要竟争锁,获得锁的线程才可以运行
# 1 event.isSet() 标志,默认值为False,event.wait()会被阻塞,当其值为True时event.wait()不会被阻塞
# 2 event.wait(timeout) # 根据标志(isSet())决定线程是否会被阻塞,默认isSet()为False,wait()是会被阻塞的。
# 3 event.set() 设置标志isSet()的值为True.此时所有wait()被阻塞的线程都会畅通。
# 4 event.clear() 设置标志isSet()的值为False,此时所有wait()的线程会被阻塞
import threading, time


def light():

    event.set()  # 打开绿灯,wait()不会被阻塞

    count = 0
    n = 0
    status = ""
    while True:
        if count < 10:
            status = "绿灯亮 ---"
        elif count < 13:
            status = "黄灯亮------"
        elif count < 20:
            event.clear()
            status = "红灯亮---------"
        else:
            count = 0
            event.set()  # 打开绿灯

        print(status)
        time.sleep(1)
        count += 1
        n += 1
        if n > 20:
            break


def car(n):
    t = 0
    while True:
        time.sleep(1)
        if event.isSet():  # 如果是绿灯
            print("car [%s] is 正在跑.." % n)
        else:
            print("car [%s] is 在等红灯.." % n)
event.wait()#去检测,标志位没有设置,卡在这
if (t > 20): break else: t += 1 if __name__ == '__main__': event = threading.Event() Light = threading.Thread(target=light) Light.start() for i in range(2): t = threading.Thread(target=car, args=(i,)) t.start()

 

五,定时器

import threading
# Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。Timer从Thread派生,没有增加实例方法
# interval: 指定的时间
# function: 要执行的方法
# args / kwargs: 方法的参数
def func(num):
    print('hello {} timer!'.format(num))

# 如果t时候启动的函数是含有参数的,直接在后面传入参数元组
timer = threading.Timer(5, func,(1,))
timer.start()
# timer.cancel() 取消定时器
print(__name__)

 六、local 和 get_ident()  注意 local是一个小写的类

import threading
import time
# 这是一个特殊的对象
ret = threading.local()  # 先实例化一个对象,注意,这是一个小写的类
ret.a = 0  # 这个对象真的很特殊,每个线程单独拥用该对象。互相不会影响

print(threading.get_ident())  # 每个线程都有一个独一无二的ident
def task(arg):  # 写个任务,加个参数
    # global ret 不能设置成全局,否则会丧失它的特殊性
    # ret = arg  # 每一个线程进来都给他开辟一个独立的空间  单纯的threading.local()的作用就是这个
    # 如果上面给ret赋值int类型,是不可以ret.a
    ret.a = arg  #
    time.sleep(1)
    print(ret.a, end='')  # 每个线程来,改了ret,然后取ret的值
    # 8405391267  顺序不一定

for i in range(10):  # i是线程的值,0 1 2 3 4 5 6 7 8 9
    t = threading.Thread(target=task, args=(i,))  # 开10个线程
    t.start()

print(threading.get_ident())  # 每个线程都有一个独一无二的ident

 

七、线程还有哪些用途,心德:

简单说几点:

1、可以用作程序暂停,time.sleep(n)暂停时间是写死了的,wait()暂停到什到时候取决于别的线程何时唤醒它

2、as3中经常使用事件驱动,python就很少使用事件,可以把线程当作事件驱动,比如你wait()以后我在完成一项任务以后notify()你

八、天哪,终于学完了,学会了

posted @ 2020-09-28 14:02  老谭爱blog  阅读(190)  评论(0)    收藏  举报