Python Threading 多线程编程

写在篇前

  threading模块是python多线程处理包,使用该模块可以很方便的实现多线程处理任务,本篇文章的基础是需要掌握进程、线程基本概念,对PV原语、锁等传统同步处理方法有一定的了解。另外,threading模块的实现是参考java多线程处理方式,并且只实现了其中的一个子集。必须说明的是,由于GIL的存在,多线程的应用主要用于IO密集型任务,不适合CPU密集型任务,如果要提高CPU的利用率,需要利用协程或则多进程编程。

Threading 方法属性

  • threading.active_count() 返回当前活动的线程数
  • threading.current_thread() 返回当前线程的Thread对象
  • threading.get_ident() 获取当前线程的(唯一)标识符
  • threading.enumerate() 返回一个包含当前处于活动状态Thread 对象的list
  • threading.main_thread() 返回当前线程的主线程
  • threading.settrace(func) 为每个线程设置一个trace函数,在调用run方法之前会被执行
  • threading.setprofile(func) 同上,为每一个线程设置一个profile函数
  • threading.stack_size([size]) 设置每个线程私有栈空间大小,默认为0,若不为0不可低于32KB(32768)
  • threading.TIMEOUT_MAX 允许线程被堵塞的最长时间,按我的理解一般用不上

Thread对象

 使用Thread一共有两种方式,同Java多线程模型:

     (1) 继承Thread类,并重写且仅仅重写__init__run方法,特别注意的是重写__init__方法首先应该调用父类构造方法Thread.__init__

     (2)将一个函数传入到Thread类的构造函数

 Thread对象的用法以及主要方法介绍,我们用以下例子来说明一下:

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 11:26'


import threading
import time


def func1(num):
    time.sleep(3)
    print(str(num)+':you can if you will')


def func2(num):
    time.sleep(2)
    print(str(num)+':the devil is in the details')


def func3(num):
    time.sleep(1)
    print(str(num)+':life is short, u need what?')


def main():
    start = time.time()
    print('start mian Thread')

    ts = (threading.Thread(group=None, target=func1, args=(1,)),
          threading.Thread(group=None, target=func2, args=(2,)),
          threading.Thread(group=None, target=func3, args=(3,)))  # 【重要】创建三个线程,分别执行func1~3

    for _t in ts:
        _t.start()  # 【重要方法】启动线程
        # _t.join()  # 【重要方法】join方法用来告诉父线程,您要等我子线程执行完毕,你再继续往下走,这里注释了,所以你应该会发现程序输出 mian Thread ends, cost 0 secs
    end = time.time()
    print('mian Thread ends, cost %d secs' % (end-start))


if __name__ == '__main__':
    main()

# 以下是执行结果
start mian Thread
mian Thread ends, cost 0 secs
3:life is short, u need what?
2:the devil is in the details
1:you can if you will

Lock对象

 因为资源总是有限的,如果多个线程对同一个对象进行操作,则有可能造成资源的争用,甚至导致死锁。引入锁,是一种便捷的解决方式。主要包括两个方法,acquire()release():

acquire(blocking=True)

  当阻塞参数设置为True(默认值)时调用,然后就会进入到locked状态直到解锁,返回True。

release()

  释放占有的锁,无返回值

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 13:56'

import threading
import time

# 临界资源
num = 0

lock = threading.Lock()


def num_add(t_i):
    global num
    time.sleep(3)
    if lock.acquire():  # 这句话相当于 if lock.acquire()
        num += 1
    lock.release()
    print('thread %d set num %d' % (t_i, num))


def num_sub(t_i):
    global num
    time.sleep(3)
    if lock.acquire():  # 这句话相当于 if lock.acquire()
        num -= 1
    lock.release()
    print('thread %d set num %d' % (t_i, num))


def main():
    ts = []
    for i in range(5):
        t = threading.Thread(target=num_add, args=(i+1,))
        t.start()
        ts.append(t)

    for i in range(5):
        t = threading.Thread(target=num_sub, args=(5+i+1,))
        t.start()
        ts.append(t)

    for t in ts:
        t.join()

    print('end')


if __name__ == '__main__':
    main()
# 以下是结果
thread 4 set num 1
thread 3 set num 2
thread 5 set num 3
thread 1 set num 4
thread 2 set num 5
thread 9 set num 4
thread 10 set num 3
thread 7 set num 2
thread 6 set num 1
thread 8 set num 0
end

RLock对象

  RLock称之为递归锁,从某种角度来讲属于一种比Lock更安全的锁,和Lock的区别在于:在同一线程内,对RLock进行多次acquire()操作(或则说RLock允许递归加锁),程序不会阻塞;而如果是Lock那么将会发生堵塞。RLock所拥有的方法同上,下面例子在上例基础上稍微改动:

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 13:56'

import threading
import time

# 临界资源
num = 0

lock = threading.RLock()  # 【重点】这里必须是RLock,不能是Lock


def num_add(t_i):
    with lock:  # 这是属于上下文管理器的一个使用用法
        global num
        time.sleep(3)
        if lock.acquire():  # 这句话相当于 if lock.acquire()
            num += 1
        lock.release()
        print('thread %d set num %d' % (t_i, num))


def num_sub(t_i):
    global num
    time.sleep(3)
    if lock.acquire():  # 这句话相当于 if lock.acquire()
        num -= 1
    lock.release()
    print('thread %d set num %d' % (t_i, num))


def main():
    ts = []
    for i in range(5):
        t = threading.Thread(target=num_add, args=(i+1,))
        t.start()
        ts.append(t)

    for i in range(5):
        t = threading.Thread(target=num_sub, args=(5+i+1,))
        t.start()
        ts.append(t)

    for t in ts:
        t.join()

    print('end')


if __name__ == '__main__':
    main()

Semaphore对象

 信号量是计算机科学最古老的一种同步原语,由荷兰计算机科学家Edsger W. Dijkstra提出,通常也称之为PV原语。在对象内部会维护一个计数器,这个计数器的初值由Semaphore初始化时给出,这个值我们一般称之为 临界资源数量。当临界资源数量大于0时,线程不会被阻塞;当临界资源等于0时再有线程请求,那将会被阻塞,直到某个线程释放临界资源。主要方法同RLock、Lock,下面用PV原语最(也许吧)经典的例子,哲学家问题来说明一下:

  哲学家问题:话说有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上,在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐;进餐完毕,放下筷子又继续思考。请设计一种方法,使哲学家不要饿死。

这里写图片描述

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 15:35'

import threading
import time


def philosopher(i, chopstick_sema, sema):
    """
    描述哲学家活动
    :type i: int 表示第几个哲学家
    :return:no return
    """
    while True:
        think(i + 1)

        sema.acquire(blocking=True)  # 请求吃饭

        chopstick_sema[i].acquire()  # 拿起左边筷子
        chopstick_sema[((i + 1) % 5)].acquire()  # 拿起右边筷子

        eat(i + 1)

        chopstick_sema[((i + 1) % 5)].release()  # 放下右边筷子
        chopstick_sema[i].release()  # 放下左边筷子

        sema.release()  # 吃饭完毕
        think(i + 1)


def eat(phil_NO):
    print('philosopher %d is eating' % phil_NO)
    time.sleep(1)
    print('philosopher %d finish eating' % phil_NO)


def think(phil_NO):
    print('philosopher %d is thinking' % phil_NO)
    time.sleep(2)
    print('philosopher %d finish thinking' % phil_NO)


def main():

    # 为了避免死锁,同时只允许四个哲学家吃饭
    sema = threading.Semaphore(value=4)
    chopstick_sema = [threading.Semaphore(value=1) for i in range(5)]
    ts = []
    for i in range(5):
        t = threading.Thread(target=philosopher, args=(i, chopstick_sema, sema))
        t.start()
        ts.append(t)

    for t in ts:
        t.join()


if __name__ == '__main__':
    main()

Condition对象

  Condition对象总是与某种锁相关联,锁对象可以通过构造函数传入,或者它会默认创建一个锁,并且锁是Condition对象的一部分,不必单独跟踪它。

acquire()

    尝试获取锁

release()

    释放已获得的锁

wait(timeout=-1)

    主动进入等待阻塞状态,直到被其他线程唤醒或则超时

wait_for(predicate, timeout=None)

    进入等待状态,直到条件被置为True,timeout参数意思如上,predicate参数表示一个callable对象。

notify()

    唤醒其中一个线程

notify_all()

     唤醒所有进程

举个例子:

    假设有一个缓冲区大小为5,要实现互斥存取数据,代码如下:
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 16:26'

import threading
import time

con = threading.Condition(threading.RLock())

num = 0


class Producer(threading.Thread):
    """
    生产者类
    """

    def __init__(self):
        """
        构造函数
        """
        threading.Thread.__init__(self)

    def run(self):
        """
        重写 run方法
        :return:
        """
        global num
        with con:
            while True:
                num += 1
                print('生产一个产品,现在有产品%d个' % num)
                time.sleep(2)
                if num >= 5:
                    print('缓冲区已满')
                    con.notify()  # 唤醒等待池
                    con.wait()  # 主动进入等待池,挂起


class Consumers(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global num
        with con:
            while True:
                num -= 1
                print("取出一个产品,还剩%d个" % num)
                time.sleep(3)
                if num <= 0:
                    print('缓冲区已空')
                    con.notify()
                    con.wait()

if __name__ == '__main__':
    p = Producer()
    c = Consumers()
    p.start()
    c.start()
    p.join()
    c.join()


Event对象

 Event,翻译作事件,个人觉得该词是计算机编程中的一个专有名词。在这里用来线程之间的通信,当某一个event发生之后,其他线程做出一定的反应;或者说某一个线程等待另一个线程某个事件的发生。每一个Event对象内部维护一个flag,表征事件是否已经发生。

is_set()

     当且仅当Event对象内部维护的flag为True时,返回True,否则返回False

set()

    将flag置为True

clear()

    将flag置为False

wait(timeout=-1)

    当flag是False时,阻塞;一旦为True或超时,立刻唤醒

举个例子,好比像心意女生表白:

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 14:58'

import threading
import time

event = threading.Event()

# 假设你想表白某位心仪女孩
# 你想,表白时送点花,更有气氛,于是乎你打电话去花店,叫老板送一束花过来
# 然后再表白


def confess_to_girl():
    print('u r preparing to confess')
    event.wait()
    print('start to confess: hi , my loving  girl, I....')


def flower_delivering():
    print('flower is delivering, plea wait')
    time.sleep(5)
    print('flower comes')
    event.set()


def main():
    t1 = threading.Thread(target=confess_to_girl)
    t2 = threading.Thread(target=flower_delivering)

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print('\nstory,maybe new start, maybe ending!')


if __name__ == '__main__':
    main()

Timer对象

 计时器,即让一个线程在一个指定的之间之后再开始执行,是属于Thread的一个封装子类,故用法基本相似。

def hello():
    print("hello, world")

t = Timer(30.0, hello)
# t.cancel()  # 在开始之前,还可以使计时器停止
t.start()  # after 30 seconds, "hello, world" will be printed

Barrier对象

  Barrier,顾名思义就是障碍的意思,只有人多力量大的时候,才能一起跨过一个个障碍,继续前行。Barrier则是一个线程障碍,只有线程数量达到指定数量之后,才能唤醒所有等待进程。这个功能不是很常用,简单介绍一下吧,主要方法有:

wait(timeout=None)

    使线程进入等待状态,直到达到一定数量,这时将会使barrier进入broken状态,从而一齐释放被堵塞的线程

reset()

    使Barrier恢复默认‘拦截’状态

abort()

    使Barrier进入Broken状态

parties

    即上面提到的 指定数量的线程,int类型

n_waiting

    正在等待的线程

broken

    bool类型,指示Barrier是否处于Broken状态

猜你喜欢

转载自blog.csdn.net/jeffery0207/article/details/82716640