Python-线程

一、线程的创建

  1. 使用threading模块创建线程(中阶的并发编程)(本章使用的方法)
  2. 利用第三方模块(高阶并发编程)

涉及到threading模块创建线程分为两种模式:

  (1)threading.Thread创建线程对象,指定两个参数 target,args。
  (2) 继承threading.Thread,重写run方法创建线程对象。

方式一:
  threading.Thread(target=,args=)
  target=要执行的函数名(要使用多线程实现执行函数)
  args=要执行函数的参数(以元组的形式传入)
  time.sleep算是IO程序

import threading,time
def mission(end):
    for i in range(end):
        print(i)
        time.sleep(0.5)
t1=threading.Thread(target=mission,args=(10,))
t2=threading.Thread(target=mission,args=(10,))
# 希望线程执行,需要将线程对象放到cpu的执行计划(执行列表)
# 对象.start
# start并不代表执行,只能代表将任务交给了cpu,cpu什么时候执行,由cpu说了算
t1.start()
t2.start()

方式二:继承threading.Thread

import threading,time
class MyThread(threading.Thread):
    def run(self):
        for i in range(10):
            print(i)
             # time.sleep(0.5)
             # print(threading.current_thread())
             # print(threading.get_ident())
             # print(threading.main_thread())

t1=MyThread()
t2=MyThread()
t1.start()
t2.start()

run方法才是真正执行线程中任务的方法。如果直接调用run方法,那么相当于让当前任务串行执行。

t1.run()
t2.run()

 

二、线程的生命周期

  1.新建:当新创建一个线程对象时,线程处于新建状态
  2.就绪:执行start方法之后,线程处于就绪状态
  3.运行:cpu将时间片分配给当前的线程对象,执行线程对应任务。
  4.阻塞:因为某些条件没有满足,处于等待的过程中,cpu会将时间片分给其他的线程。比如sleep(1),当睡1s之后,只能代表当前线程被重新加入到cpu的执行列表中。并不代表会马上执行。
  5.死亡:当线程run方法执行完毕,或者run方法中抛出了异常没有被捕获,程序意外终止。

 

三、线程的相关操作

  1 threading.active_count()  当前活跃线程数量,(处于就绪之后,死亡之前)

print("当前活跃线程的数量",threading.active_count())

  2.threading.enumerate() 返回一个列表,包含活跃的线程

li=threading.enumerate()
for i in li:
    print(i)

  3.threading.current_thread() 当前执行的线程

print(threading.current_thread())

  4.threading.get_ident() 线程标志,一个序号,独一无二的序号

  5.threading.main_thread() 返回执行解释器的线程(主线程)。对于一个进程来说,只有一个主线程。 if __name__=="__main__":

print(threading.main_thread())

  6. start 就绪,加入到cpu的执行列表中,等待执行
  7. run(),当线程获得时间片之后,会执行的方法

  8. 线程对象.join(参数) 线程抢占。B.join(参数)  在A 线程中,如果调用了B线程的join方法,B线程抢占时间片
  参数:抢占的时间,不写一直抢占时间片到B执行结束

def mission():
    print("休眠开始")
    time.sleep(2)
    print("休眠结束")

t=threading.Thread(target=mission)
t.start()
t.join()
print("主线程执行")

 

例子:修路的例子

def mission():
    print("修路开始")
    print("修路过程中....")
    time.sleep(2)
    print("修路结束")

t=threading.Thread(target=mission)
print("start之前ident=",t.ident)
t.start()
print(t.is_alive())
print("start之后ident=",t.ident)
t.name="过马路的线程"
print("想要过马路")
t.join()
print("路修好了,可以过马路。。。。")
print(t.name)
print(t.is_alive())

  9.name 线程中的一个私有属性,线程名字。get和set ,被property化

  10.ident 线程标志  属性,get_ident  ===ident 被propery化 。没有提供set方法,只有线程start启动之后,才有ident标志

  11. is_alive() 判断是否存活

  12.daemon 设置是否是守护线程(后台线程)


  守护线程
  有一个唱歌,乐队伴奏
  两种情况
    (1)将乐队伴奏设置为非守护线程(默认,前台线程)谁也不等谁(cpu采用异步模式对待每个线程)。唱歌的人唱完了,乐队伴奏如果还没有伴奏完毕,会继续伴奏

      (2)将乐队线程设置为守护线程。唱歌的人唱完了,乐队伴奏不管有没有演奏完毕,都不会继续伴奏。如果将一个线程设置成守护线程:意味着告诉处理器,不用顾及当前的这个线程,当其他的非守护线程 退出的时候,守护线程也会跟着退出。
python中垃圾回收机制,使用守护线程

def music():
    print("乐队线程开始执行")
    time.sleep(1)
    print("乐队持续在伴奏")
    print("乐队持续在伴奏")
    print("乐队持续在伴奏")
    print("乐队持续在伴奏")
    print("乐队持续在伴奏")
    print("乐队结束")
if __name__=="__main__":
    mt=threading.Thread(target=music)
    # 守护线程需要在start之前设置
    mt.setDaemon(True)
    mt.start()
    print("开始唱歌")
    print("唱歌中...")
    print("唱歌结束")


四、线程同步

  解决:多线程中出现的资源共享问题。

1. 并发修改出现问题

票就是共享资源

ticket=100
def buy_ticket():
    global ticket
    while ticket>0:
        time.sleep(0.5)
        print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
        ticket-=1

t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()

2. 线程锁

  希望在同一个时间点内,一片共享资源只希望被一个线程访问。

方式:lock=threading.Lock()
加锁:lock.acquire()
解锁:lock.release()

第一次解决: while循环可能同时进入了其他两个线程

ticket=100
lock=threading.Lock()
def buy_ticket():
    global ticket
    while ticket>0:
        lock.acquire()
        # time.sleep(0.5)
        print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
        ticket-=1
        lock.release()

t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()

第二次  问题原因:张三进入之后加锁,循环将所有的排票都抢完毕

ticket=100
lock=threading.Lock()
def buy_ticket():
    global ticket
    lock.acquire()
    while ticket>0:
        # time.sleep(0.5)
        print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
        ticket-=1
    lock.release()

t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()

第三次: 没有人解最后锁

ticket=100
lock=threading.Lock()
def buy_ticket():
    global ticket
    while True:
        lock.acquire()
        if ticket>0:
            # time.sleep(1)
            print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
            ticket-=1
        else:
            break
        lock.release()

t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()

第四次

ticket=100
lock=threading.Lock()
def buy_ticket():
    global ticket
    while True:
        try:
            lock.acquire()
            if ticket>0:
                time.sleep(0.2)
                print("{}抢到了第{}票".format( threading.current_thread().name,ticket))
                ticket-=1
            else:
                break
        finally:
            lock.release()

t1=threading.Thread(target=buy_ticket)
t1.name="张三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()

 

五、死锁

  死锁的定义:当两个或者多个线程同时拥有自己的资源,又互相等待对方的资源,导致程序永远先入僵持状态

共有两把锁
A----锁1-----希望拥有锁2
B----锁2-----希望拥有锁1

多线程并发访问数据的时候,要在共享资源上加锁。如果加的锁不只一把,可能会出现死锁

import threading,time
lock1=threading.Lock()
lock2=threading.Lock()

def mission(l1,l2): #l1,l2代表传入的两把锁
    l1.acquire()
    print("{}获得了{}".format(threading.current_thread().name,id(l1)))
    time.sleep(1)
    l2.acquire()
    l1.release()
    l2.release()
a=threading.Thread(target=mission,args=(lock1,lock2))
b=threading.Thread(target=mission,args=(lock2,lock1))
a.start()
b.start()

 

六、通知和等待

  抢票,一个资源(生产者),多个线程共享。
生产者和消费者。
使用from threading import  Condition 的锁,不仅有获取和释放的方法
还有:
wait     :等待, 会释放当前线程占用的锁(跟time.sleep不一样,sleep,不释放锁)
notifyall:通知所有等待的线程加入cpu执行列表
notify方法:任选一个等待线程,加入cpu执行列表

需求:
生产者:生产商品,让商品+1,定义一个列表仓库,每次列表中加入一个元素,当做生产一个商品
消费者:消费商品,让商品-1,让列表中元素被删除
当供过于求:生产者生产太快,商品在仓库中达到3件之后,生产者不能再生产,需要被阻塞。被唤醒的时机:只有要商品被消费了
求大于供:消费者消费太快,商品在仓库中0个,消费者就不能再消费,需要被阻塞。被唤醒的时机:只要有商品被生产了

from threading import Condition
lock=threading.Condition()
def produce(li):
    i=0
    while  True:
        try:
            lock.acquire()
            if len(li)==3:
                print("仓库已满,生产阻塞")
                lock.wait()
            else:
                li.append("商品{}".format(i))
                i+=1
                print("生产了{}商品".format(i))
                lock.notify_all()
        finally:
            lock.release()

def consume(li):
    while True:
        try:
            lock.acquire()
            if  len(li)==0:
                print("仓库已空,消费阻塞")
                lock.wait()
            else:
                print("消费了{}商品".format(li.pop(0)))
                lock.notify_all()
        finally:
            lock.release()

li=[]
t1=threading.Thread(target=produce,args=(li,))
t2=threading.Thread(target=consume,args=(li,))
t1.start()
t2.start()

七、队列(线程)

  队列数据类型,内部实现了锁的机制,队列多用于多线程的并发

队列分为三种:

  ①先进先出队列:队列
  ②先进后出队列:堆栈
  ③优先队列:按照优先级出队列

(1) 先进先出队列
queue.Queue(size):size=0或者负数,表示无限容量,如果size有值,代表最大容量

q=queue.Queue(3)

通过q.qsize() 返回队列中元素的格式

print(q.qsize())

q.empty队列是否为空

print(q.empty())

q.full() 判断队列是否已满

print(q.full())

q.put向队列中添加元素
item:要添加的元素
block:继续向队列中添加元素的时候,如果队列已满,put方法是否是处于阻塞状态(默认True)block如果=False,当队列已满的时候 ,再继续添加元素,则会报错。
timeout:队列已满,如果在指定的时间内(单位:秒),仍然无法添加元素,则会产生异常

q.put("hello")
q.put("world",block=False)

put_nowait 代表只要队列已满,put函数执行的时候会报错。
q.put_nowait(item)  # 等价于put(item,block=False)
q.get() 向外取元素(规则就是先进先出)

q.put("python")
print(q.get())
print(q.get())
print(q.get())
print(q.get())

如果队列是空队列,继续向外get元素,get方法是默认的阻塞函数。用法跟put中一样。
q.get(block=False)
q.get_nowait()====q.get(block=False)

(2) 先进后出,堆栈

lq=queue.LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())
print(lq.get())
print(lq.get())
print(lq.get())
while not q.empty():
    print(q.get())

(3) 优先队列
  不是按照传统队列的先进先出,或者后进先出,而是根据队列的优先级别进行排列。进的时候,正常进入 ,出的时候是按照优先级别出。优先级队列中的元素必须支持元素之间的比较。

print("abc"<"bcd")
print((1,2,4)<(3,4))
q=queue.PriorityQueue()
q.put("clock")
q.put("banana")
q.put("egg")
q.put("apple")
while not q.empty():
    print(q.get())

q.put((1,2,3))
q.put((2,2,3))
q.put((3,2,3))
q.put((-1,2,3))
while not q.empty():
    print(q.get())

q=queue.PriorityQueue()
q.put((2,"clock"))
q.put((1,"banana"))
q.put((3,"egg"))
q.put((4,"apple"))
# q.put("a") 
while not q.empty():
    print(q.get())

应用队列实现生产者和消费者的例子

import queue
import threading
def produce(q):
    i=1
    while True:
        q.put(i)
        print("生产商品{}".format(i))
        i+=1
        time.sleep(0.5)

def consume(q,name):
    while True:
        print("{}消费了{}".format(name,q.get()))
        time.sleep(0.1)
# 队列本身创建的时候就有容量的设置
q=queue.Queue(3)
t1=threading.Thread(target=produce,args=(q,))
t2=threading.Thread(target=consume,args=(q,"tom"))
t3=threading.Thread(target=consume,args=(q,"jerry"))
t1.start()
t2.start()
t3.start()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章