Python--threading模块

1.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入。

import time
from threading import Thread

# 自定义线程函数。
def main(name="Python"):
    for i in range(2):
        print("hello", name)
        time.sleep(1)

# 创建线程01,不指定参数
thread_01 = Thread(target=main)
# 启动线程01
thread_01.start()


# 创建线程02,指定参数,注意逗号
thread_02 = Thread(target=main, args=("MING",))
# 启动线程02
thread_02.start()



threading.Thread类的初始化函数原型:

	
def __init__(self, group=None, target=None, name=None, args=(), kwargs={})

          参数group是预留的,用于将来扩展;
          参数target是一个可调用对象(也称为活动[activity]),在线程启动后执行;
          参数name是线程的名字。默认值为“Thread-N“,N是一个数字。
          参数args和kwargs分别表示调用target时的参数列表和关键字参数。
import time
from threading import Thread

class MyThread(Thread):
    def __init__(self, name="Python"):
        # 注意,super().__init__() 一定要写
        # 而且要写在最前面,否则会报错。
        super().__init__()
        self.name=name

    def run(self):
        for i in range(2):
            print("hello", self.name)
            time.sleep(1)

if __name__ == '__main__':
    # 创建线程01,不指定参数
    thread_01 = MyThread()
    # 创建线程02,指定参数
    thread_02 = MyThread("MING")

    thread_01.start()
    thread_02.start()

线程常用方法:

t=Thread(target=func)

# 启动子线程
t.start()

# 调用Thread.join将会使主调线程堵塞,直到被调用线程运行结束或超时
t.join()

# 判断线程是否在执行状态,在执行返回True,否则返回False
t.is_alive()
t.isAlive()

# 设置线程是否随主线程退出而退出,默认为False
t.daemon = True
t.daemon = False

# 设置线程名
t.name = "My-Thread"

Thread.getName()
Thread.setName()
Thread.name
Thread.ident
获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。

2.多线程中锁机制:为什么加锁,多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了:

原因是因为高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:

a= a+ 1

也分两步:

  1. 计算a+1,存入临时变量中;
  2. 将临时变量的值赋给a
  3. 两个线程都是去修改a的值,如果一个线程在执行1后,被另一个线程抢去执行了执行操作a=a-1后,a=-1,然后线程1执行临时变量赋值操作,最后执行结果a=1
    线程1执行 a=a+1,实际是两步操作:x=a+1,a=x
    线程2执行a=a-1  实际是两步操作:x=a-1,a=x
    执行过程遇到下述情景:
    初始化a=0
    
    线程1  x=a+1, x=1,a=0
    此时线程2抢去执行权
    x2=a-1, a=0,x2=-1
    a=x2,a=-1
    轮到线程1执行
    a=x,x=1,a=1
    结果 a=1
    
    应该是a=a+1后台执行的两步操作要一起执行的,现在没有一起执行,所以产生了问题,就比如是银行转账,必须是收款方账户增加,付款方账户减少才算一次转账成功,多线程可能产生后果是一方账户减少了,此时开始了第二次转账,收款方账户没有增加。这两个操作必须一块执行,要么都成功,要么都失败,称为原子性。
    
import threading

# 生成锁对象,全局唯一
lock = threading.Lock()

# 获取锁。未获取到会阻塞程序,直到获取到锁才会往下执行
lock.acquire()

# 释放锁,归回倘,其他人可以拿去用了
lock.release()

使用上下文管理器
import threading

lock = threading.Lock()
with lock:
    # 这里写自己的代码
    pass

两种类型的琐:threading.Lock和threading.RLock两种琐的主要区别是:RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐

threading.Condition

可以把Condiftion理解为一把高级的琐,它提供了比Lock, RLock更高级的功能,允许我们能够控制复杂的线程同步问题。threadiong.Condition在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。Condition还提供了如下方法(特别要注意:这些方法只有在占用琐(acquire)之后才能调用,否则将会报RuntimeError异常。):
Condition.wait([timeout]):

wait方法释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时(如果提供了timeout参数的话)。当线程被唤醒并重新占有琐的时候,程序才会继续执行下去。
Condition.notify():

唤醒一个挂起的线程(如果存在挂起的线程)。注意:notify()方法不会释放所占用的琐。

?

1

2

Condition.notify_all()

Condition.notifyAll()

唤醒所有挂起的线程(如果存在挂起的线程)。注意:这些方法不会释放所占用的琐。

现在写个捉迷藏的游戏来具体介绍threading.Condition的基本使用。假设这个游戏由两个人来玩,一个藏(Hider),一个找(Seeker)。游戏的规则如下:1. 游戏开始之后,Seeker先把自己眼睛蒙上,蒙上眼睛后,就通知Hider;2. Hider接收通知后开始找地方将自己藏起来,藏好之后,再通知Seeker可以找了; 3. Seeker接收到通知之后,就开始找Hider。Hider和Seeker都是独立的个体,在程序中用两个独立的线程来表示,在游戏过程中,两者之间的行为有一定的时序关系,我们通过Condition来控制这种时序关系。
 

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

#---- Condition

#---- 捉迷藏的游戏

import threading, time

class Hider(threading.Thread):

  def __init__(self, cond, name):

    super(Hider, self).__init__()

    self.cond = cond

    self.name = name

    

  def run(self):

    time.sleep(1)

#确保先运行Seeker中的方法 

      

    self.cond.acquire()

#b 

    print self.name + ': 我已经把眼睛蒙上了'

    self.cond.notify()

    self.cond.wait()

#c 

              

#f

    print self.name + ': 我找到你了 ~_~'

    self.cond.notify()

    self.cond.release()

               

#g

    print self.name + ': 我赢了' 

#h

      

class Seeker(threading.Thread):

  def __init__(self, cond, name):

    super(Seeker, self).__init__()

    self.cond = cond

    self.name = name

  def run(self):

    self.cond.acquire()

    self.cond.wait() 

#a  #释放对琐的占用,同时线程挂起在这里,直到被notify并重新占

有琐。

               

#d

    print self.name + ': 我已经藏好了,你快来找我吧'

    self.cond.notify()

    self.cond.wait() 

#e

               

#h

    self.cond.release()

    print self.name + ': 被你找到了,哎~~~'

      

cond = threading.Condition()

seeker = Seeker(cond, 'seeker')

hider = Hider(cond, 'hider')

seeker.start()

hider.start()

threading.Event

Event实现与Condition类似的功能,不过比Condition简单一点。它通过维护内部的标识符来实现线程间的同步问题。(threading.Event和.NET中的System.Threading.ManualResetEvent类实现同样的功能。)
Event.wait([timeout])

堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数timeout)。
Event.set()

将标识位设为Ture
Event.clear()

将标识伴设为False。
Event.isSet()

判断标识位是否为Ture。

下面使用Event来实现捉迷藏的游戏(可能用Event来实现不是很形象)

 

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

#---- Event

#---- 捉迷藏的游戏

import threading, time

class Hider(threading.Thread):

  def __init__(self, cond, name):

    super(Hider, self).__init__()

    self.cond = cond

    self.name = name

    

  def run(self):

    time.sleep(1)

#确保先运行Seeker中的方法 

      

    print self.name + ': 我已经把眼睛蒙上了'

      

    self.cond.set()

      

    time.sleep(1

      

    self.cond.wait()

    print self.name + ': 我找到你了 ~_~'

      

    self.cond.set()

                

    print self.name + ': 我赢了'

      

class Seeker(threading.Thread):

  def __init__(self, cond, name):

    super(Seeker, self).__init__()

    self.cond = cond

    self.name = name

  def run(self):

    self.cond.wait()

              

    print self.name + ': 我已经藏好了,你快来找我吧'

    self.cond.set()

      

    time.sleep(1)

    self.cond.wait()

                

    print self.name + ': 被你找到了,哎~~~'

      

cond = threading.Event()

seeker = Seeker(cond, 'seeker')

hider = Hider(cond, 'hider')

seeker.start()

hider.start()

threading.Timer

threading.Timer是threading.Thread的子类,可以在指定时间间隔后执行某个操作。下面是Python手册上提供的一个例子:
 

?

1

2

3

4

5

def hello():

  print "hello, world"

t = Timer(3, hello)

t.start()

# 3秒钟之后执行hello函数。

threading模块中还有一些常用的方法没有介绍:

?

1

2

threading.active_count()

threading.activeCount()

获取当前活动的(alive)线程的个数。

?

1

2

threading.current_thread()

threading.currentThread()

获取当前的线程对象(Thread object)。

?

1

threading.enumerate()

获取当前所有活动线程的列表。
threading.settrace(func)

设置一个跟踪函数,用于在run()执行之前被调用。

?

1

threading.setprofile(func)

设置一个跟踪函数,用于在run()执行完毕之后调用。

Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核

死锁:线程1,嵌套获取A,B两个锁,线程2,嵌套获取B,A两个锁。 由于两个线程是交替执行的,是有机会遇到线程1获取到锁A,而未获取到锁B,在同一时刻,线程2获取到锁B,而未获取到锁A。由于锁B已经被线程2获取了,所以线程1就卡在了获取锁B处,由于是嵌套锁,线程1未获取并释放B,是不能释放锁A的,这是导致线程2也获取不到锁A,也卡住了。两个线程,各执一锁,各不让步。造成死锁。

线程间通信:

线程中通信方法大致有如下三种: - threading.Event - threading.Condition - queue.Queue

vent = threading.Event()

# 重置event,使得所有该event事件都处于待命状态
event.clear()

# 等待接收event的指令,决定是否阻塞程序执行
event.wait()

# 发送event指令,使所有设置该event事件的线程执行
event.set()


import time
import threading


class MyThread(threading.Thread):
    def __init__(self, name, event):
        super().__init__()
        self.name = name
        self.event = event

    def run(self):
        print('Thread: {} start at {}'.format(self.name, time.ctime(time.time())))
        # 等待event.set()后,才能往下执行
        self.event.wait()
        print('Thread: {} finish at {}'.format(self.name, time.ctime(time.time())))


threads = []
event = threading.Event()

# 定义五个线程
[threads.append(MyThread(str(i), event)) for i in range(1,5)]

# 重置event,使得event.wait()起到阻塞作用
event.clear()

# 启动所有线程
[t.start() for t in threads]

print('等待5s...')
time.sleep(5)

print('唤醒所有线程...')
event.set()

Thread: 1 start at Sun May 13 20:38:08 2018
Thread: 2 start at Sun May 13 20:38:08 2018
Thread: 3 start at Sun May 13 20:38:08 2018
Thread: 4 start at Sun May 13 20:38:08 2018

等待5s...

唤醒所有线程...
Thread: 1 finish at Sun May 13 20:38:13 2018
Thread: 4 finish at Sun May 13 20:38:13 2018
Thread: 2 finish at Sun May 13 20:38:13 2018
Thread: 3 finish at Sun May 13 20:38:13 2018


可见在所有线程都启动(start())后,并不会执行完,而是都在self.event.wait()止住了,需要我们通过event.set()来给所有线程发送执行指令才能往下执行
cond = threading.Condition()

# 类似lock.acquire()
cond.acquire()

# 类似lock.release()
cond.release()

# 等待指定触发,同时会释放对锁的获取,直到被notify才重新占有琐。
cond.wait()

# 发送指定,触发执行
cond.notify()
import threading, time

class Hider(threading.Thread):
    def __init__(self, cond, name):
        super(Hider, self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        time.sleep(1)  #确保先运行Seeker中的方法
        self.cond.acquire()

        print(self.name + ': 我已经把眼睛蒙上了')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 我找到你了哦 ~_~')
        self.cond.notify()

        self.cond.release()
        print(self.name + ': 我赢了')

class Seeker(threading.Thread):
    def __init__(self, cond, name):
        super(Seeker, self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        self.cond.acquire()
        self.cond.wait()
        print(self.name + ': 我已经藏好了,你快来找我吧')
        self.cond.notify()
        self.cond.wait()
        self.cond.release()
        print(self.name + ': 被你找到了,哎~~~')

cond = threading.Condition()
seeker = Seeker(cond, 'seeker')
hider = Hider(cond, 'hider')
seeker.start()
hider.start()

cond来通信,阻塞自己,并使对方执行。从而,达到有顺序的执行。 看下结果

hider:   我已经把眼睛蒙上了
seeker:  我已经藏好了,你快来找我吧
hider:   我找到你了 ~_~
hider:   我赢了
seeker:  被你找到了,哎~~~

from queue import Queue
# maxsize默认为0,不受限
# 一旦>0,而消息数又达到限制,q.put()也将阻塞
q = Queue(maxsize=0)

# 阻塞程序,等待队列消息。
q.get()

# 获取消息,设置超时时间
q.get(timeout=5.0)

# 发送消息
q.put()

# 等待所有的消息都被消费完
q.join()

# 以下三个方法,知道就好,代码中不要使用

# 查询当前队列的消息个数
q.qsize()

# 队列消息是否都被消费完,True/False
q.empty()

# 检测队列里消息是否已满
q.full()
线程间通信实际上是在线程间传递对象引用。如果你担心对象的共享状态,那你最好只传递不可修改的数据结构(如:整型、字符串或者元组)或者一个对象的深拷贝。


import threading # 导入线程包
from queue import Queue
import time
 
# 爬取文章详情页
def get_detail_html(detail_url_list, id):
    while True:
        url = detail_url_list.get()
        time.sleep(2)  # 延时2s,模拟网络请求
        print("thread {id}: get {url} detail finished".format(id=id,url=url))
 
# 爬取文章列表页
def get_detail_url(queue):
    for i in range(10000):
        time.sleep(1) # 延时1s,模拟比爬取文章详情要快
        queue.put("http://projectedu.com/{id}".format(id=i))
        print("get detail url {id} end".format(id=i))
 
if __name__ == "__main__":
    detail_url_queue = Queue(maxsize=1000)
    # 先创造两个线程
    thread = threading.Thread(target=get_detail_url, args=(detail_url_queue,))
    html_thread= []
    for i in range(3):
        thread2 = threading.Thread(target=get_detail_html, args=(detail_url_queue,i))
        html_thread.append(thread2)
    start_time = time.time()
    # 启动两个线程
    thread.start()
    for i in range(3):
        html_thread[i].start()
    # 等待所有线程结束
    thread.join()
    for i in range(3):
        html_thread[i].join()
 
    print("last time: {} s".format(time.time()-start_time))
#!usr/bin/python
# -*- coding: utf-8 -*-
# coding=utf-8
import threading
import time

def chiHuoGuo(people):
    print("%s 吃火锅的小伙伴-羊肉:%s" % (time.ctime(),people))
    time.sleep(1)
    print("%s 吃火锅的小伙伴-鱼丸:%s" % (time.ctime(),people))


class myThread (threading.Thread):   # 继承父类threading.Thread
    def __init__(self, people, name):
        '''重写threading.Thread初始化内容'''
        threading.Thread.__init__(self)
        self.threadName = name
        self.people = people

    def run(self):   # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
        '''重写run方法'''
        print("开始线程: " + self.threadName)

        chiHuoGuo(self.people)     # 执行任务
        print("结束线程: " + self.name)

print("开始执行")

# 创建新线程
thread1 = myThread("xiaoming", "Thread-1")
thread2 = myThread("xiaowang", "Thread-2")

# # 守护线程setDaemon(True)# 守护线程是确保子线程在主线程执行完毕后能够退出,而不是继续执行
# thread1.setDaemon(True)       # 必须在start之前
# thread2.setDaemon(True)

# 开启线程
thread1.start()
thread2.start()
# 阻塞主线程,等子线程结束
thread1.join()         #join是确保子线程执行完毕后才执行主线程
thread2.join()

time.sleep(0.1)
print("退出主线程")

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值