多线程

  • threading模块提供 Thread类和各种同步原语,用于编写多线程的程序。

Thread对象

  • Thread类用于表示单独的控制线程。使用下面的函数可以创建一个新线程。
  • Thread(group=None, target=None, namesNone, args=(), kwargs=())
  • Group的值是None,为以后的扩展而保留。
  • target是一个可调用对象,线程启动时,run(方法将调用此对象,它的默认值是None,表示不调用任何内容。
  • name是线程名称。默认将创建一个" Thread-N格式的唯一名称。
  • args是传递给 target函数的参数元组。
  • kwargs是传递给 target的关键字参数的字典。

Thread实例t支持以下方法和属性。

  1. t.start():通过在一个单独的控制线程中调用run()方法,启动线程。此方法只能调用一次。

  2. t.run():线程启动时将调用此方法。默认情况下,它将调用传递到构造函数中的目标函数。还可以在 Thread的子类中重新定义此方法。

  3. t.join([timeout]):等待直到线程终止或者出现超时为止。 timeout是一个浮点数,用于指定以秒为单位的超时时间。线程不能连接自身,而且在线程启动之前就连接它将出现错误。

  4. t.is_alive():如果线程是活动的,返回rue,否则返回 False。从 start()方法返回的那一刻开始,线程就是活动的,直到它的run()方法终止为止。

  5. t.name:线程名称

  6. t.ident:整数线程标识符。如果线程尚未启动,它的值为None。

  7. t.daemon:线程的布尔型后台标志。必须在调用 start()方法之前设置这个标志,它的初始值从创建线程的

后台状态继承而来。当不存在任何活动的非后台线程时,整个 Python程序将退出。所有程序都有一个

主线程,代表初始的控制线程,它不是后台线程。

  • 如何以线程的形式创建和启动一个函数(或其他可调用对象)

直接调用线程:

import threading
import time
def sayhi(num):
    print("启动第几个:%s"%num)

    time.sleep(3)
if __name__ == '__main__':
    t1 = threading.Thread(target=sayhi,args=(1,))
    t2 = threading.Thread(target=sayhi,args=(2,))

    t1.start()
    t2.start()
    print(t1.getName())
    print(t2.getName())

继承式调用:

import threading
import time
class myThread():
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num

    def run(self):
        print("启动第几个:%s" %self.num)
        time.sleep(3)
if __name__ == '__main__':
    t1 = myThread(1)
    t2 = myThread(2)


    t1.run()
    t2.run()

Timer对象

  • Timer对象用于在稍后的某个时间执行一个函数。
  • Timer(interval, func [, args [, kwargs]])
  • 创建定时器对象,在过去 interval秒时间之后运行函数 func. args和 kwargs提供传递给func的参数和关键字参数。在调用 start()方法后才会启动定时器。
  • Timer对象t具有以下方法。
  1. t.start:启动定时器。提供给Timer()方法的函数func将在指定的时间间隔之后执行。
  2. t.cancel():如果函数尚未执行,取消定时器

Lock对象

  • 互斥锁,两个方法 acquire()和 releaseω)用于修改锁的状态。如果状态为已锁定,尝试获取锁将被阻塞,直到锁被释放为止。如果有多个线程等待获取锁,当锁被释放时,只有一个线程能获得它。等待线程获得锁的顺序没有定义。
  • 使用下面的构造函数可以创建新的Lock实例:
    lock = thraeding.Lock()
  • 创建新的Lock对象,初始状态为未锁定。
  • Lock实例1ock支持以下方法。
  • lock.acquire([blocking 1):获取锁,如果有必要,需要阻塞到锁释放为止。如果提供 blocking参数并将它设为 False,当无法获取锁时将立即返回 False,如果成功获取锁则返回True。
  • lock.release()
  • 释放一个锁。当锁处于未锁定状态时,或者从与原本调用 acquire()方法的线程不同的线程调用此方法,将出现错误。

Rlock对象

  • 可重入锁是一个类似于Lock对象的同步原语,但同一个线程可以多次获取它。这允许拥有锁的线程执行嵌套的 acquire()和 release()操作。在这种情况下,只有最外面的 release()操作才能将锁重置为未锁定状态。
    使用下面的构造函数可以创建一个新的 RLock对象:
rlock = threading.RLock()
  • 创建新的可重入锁对象。 RLock对象 rlock支持以下方法。
  • rlock.acquire([blocking 1):获取锁,如果有必要,需要阻塞到锁被释放为止。如果没有线程拥有锁,它将被锁定,而且递归级别被置为1。如果此线程已经拥有锁,锁的递归级别加1,而且函数立即返回.
  • rlock.release()通过减少锁的递归级别来释放它。如果在减值后递归级别为0,锁将被重置为未锁定状态。否则,锁将保持已锁定状态。只能由目前拥有锁的线程来调用此函数。

信号量

  • 信号量是一个基于计数器的同步原语,每次调用 acquire()方法时此计数器减1,每次调用release()方法时此计数器加1。如果计数器为0, acquire()方法将会阻塞,直到其他线程调用re1ease()方法为止。
  • Semaphore([value])
  • 创建一个新的信号量。ⅴa1ve是计数器的初始值。如果省略此参数,计数器的值将被置为l。
s = threading.Semaphore()
  • Semaphore实例s支持以下方法。
  • s.acquire([blocking]):获取信号量。如果进入时内部计数器大于0,此方法将把它的值减1,然后立即返回。如果它的值为0,此方法将阻塞,直到另一个线程调用 release()方法为止。 blocking参数的行为与Lock和 RLock
    对象中描述的相同。
  • s.release()通过将内部计数器的值加1来释放一个信号量。如果计数器为0,而且另一个线程正在等待,该线程将被唤醒。如果有多个线程正在等待,只能从它的acquire(调用返回其中一个。线程释放的顺序并不确定。

有边界的信号量

  • BoundedSemaphore([value])
  • 创建一个新的信号量。va1ue是计数器的初始值。如果省略此参数,计数器的值将被置为1。
  • BoundedSemaphore的工作方式与 Semaphore完全相同,但 release()操作的次数不能超过 acquire()操作的次数
  • 信号量与互斥锁之间的微妙差别在于:信号量可用于发信号。例如,可以从不同线程调用 acquire()和 release()方法,以便在生产者和消费者线程之间进行通信。

事件

  • 事件用于在线程之间通信。一个线程发出“事件”信号,一个或多个其他线程等待它。Event实例管理着一个内部标志,可以使用set()方法将它置为rue,或者使用 clear()方法将它重置为 False,wait()方法将阻塞,直到标志为Ture。
e = threading.Event()
  • 创建新的 Event实例,并将内部标志置为Fa1se。Event实例e支持以下方法。
  1. e.is_set():只有当内部标志为True时才返回True。
  2. e.set():将内部标志置为rue。等待它变为True的所有线程都将被唤醒。
  3. e.clear():将内部标志重置为Fase。
  4. e.wait([timeout]):阻塞直到内部标志为rue。如果进入时内部标志为rue,此方法将立即返回。否则,它将阻塞,直到另一个线程调用set()方法将标志置为True,或者直到出现可选的超时。 timeout是一个浮点数,
    用于指定以秒为单位的超时期限。
  5. 尽管 Event对象可用于给其他线程发信号,但不应该使用它们来实现在生产者/消费者问题中十分典型的通知。

条件变量

  • 当需要线程关注特定的状态变化或事件的发生时将使用这个锁。典型的用法是生产者-消费者问题,其中一个线程生产的数据供另一个线程使用。
  • 使用下面的构造函数可以创建新的 Condition实例:
  • Condition([lock])
  • 创建新的条件变量。lock是可选的Lock或RLock实例。如果未提供lock参数,就会创建新的RLock实例供条件变量使用。
cv = threading.Condition()
  • 条件变量cv支持以下方法。
  1. cv.acquire(*args): 获取底层锁。此方法将调用底层锁上对应的 acquire(*args)方法。
  2. cv.release():释放底层锁。此方法将调用底层锁上对应的 release()方法。
  3. cv.wait([timeout]):等待直到获得通知或出现超时为止。此方法在调用线程已经获取锁之后调用。调用时,将释放底层锁,而且线程将进入睡眠状态,直到另一个线程在条件变量上执行 notify(或 notify110)方法将其唤醒为止。在线程被唤醒之后,线程将重新获取锁,方法也会返回。timeout是浮点数,单位为秒。如果这段时限耗尽,线程将被唤醒,重新获取锁,而控制将被返回
  4. cv.notify([n]):唤醒一个或多个等待此条件变量的线程。此方法只会在调用线程已经获取锁之后调用,而且如果没有正在等待的线程,它就什么也不做。n指定要唤醒的线程数量,默认为1。被唤醒的线程在它们重新获取锁之前不会从wait()调用返回.
  5. cv.notify_all():唤醒所有等待此条件的线程。

queue

  • queue模块实现了各种多生产者-多消费者队列,可用于在执行的多个线
    程之间安全地交换信息。
  • queue模块定义了3种不同的队列类。

Queue([maxsize])
创建一个FFO( first- in first-out,先进先出)队列。 maXsize是队列中可以放入的项的最大数量。
如果省略 maxsize参数或将它置为0,队列大小将为无穷大。

Lifoqueue([maxsize])
创建一个LIFO( last-in first-out,后进先出)队列(也叫栈)。

Priorityqueue([maxsize])
创建一个优先级队列,其中项按照优先级从低到高依次排好。使用这种队列时,项应该是(priority,data)形式的元组,其中 priority是一个数字。

import queue
q = queue()
  • 队列类的实例q具有以下方法。
  1. q.qsize():返回队列的正确大小。因为其他线程可能正在更新队列,此方法返回的数字不完全可靠。
  2. q.empty():如果队列为空,返回True,否则返回Fa1se。
  3. q.full():如果队列已满,返回rue,否则返回 False。
  4. q.put(item [block [,timeout]]):将item放入队列。如果可选参数bock为True(默认值),调用者将被阻塞直到队列中出现可用的空闲位置为止。否则(block为 False),队列满时将引发Full异常。timeout提供可选的超时值,单位为秒。如果出现超时,将引发Full异常。
  5. q.put_nowait(item):等价于q.put(item, False)方法。
  6. q.get([block [,timeout]]):从队列中删除一项,然后返回这个项。如果可选参数blok为rue(默认值),调用者将阻塞,直到队列中出现可用的空闲位置。否则(block为False),队列为空时将引发Empty异常。timeout提供可选的超时值,单位为秒。如果出现超时,将引发Empty异常。
  7. q.get_nowait():等价于get(0)方法
  8. q.task_done():队列中数据的消费者用来指示对于项的处理已经结束。如果使用此方法,那么从队列中删除的每一项都应该调用一次。
  9. q.join():阻塞直到队列中的所有项均被删除和处理为止。一旦为队列中的每一项都调用了一次q. task done()方法,此方法将会直接返回。

使用队列的线程示例

import threading
from queue import Queue
class WorkerThread(threading.Thread):
        def __init__(self,*args, **kwargs):
            threading.Thread.__init__(self, *args, **kwargs)
            self.input_queue= Queue()
        def send(self, item):
            self.input_queue.put(item)
        def close(self):
            self.input_queue.put(None)
            self.input_queue. oin()
        def run(self):
            while True:
                item = self.input_queue.get()
                if item is None:
                    break
                print(item)
                self.input_queue.task_done()
            self.input_queue.task_done()
            return

if __name__ == '__main__':
    w = WorkerThread()
    w.start()
    w.send('hello')
    w.send("world")
    w.close()

多线程共享全局变量的问题:

多线程都是在同一个进程中运行的。因此在进程中的全局变量所有线程都是可共享的。这就造成了一个问题,因为线程执行的顺序是无序的。有可能会造成数据错误

解决方案:锁机制

为了解决以上使用共享全局变量的问题。threading提供了一个Lock类,这个类可以在某个线程访问某个变量的时候加锁,其他线程此时就不能进来,直到当前线程处理完后,把锁释放了,其他线程才能进来处理。

posted @ 2019-04-21 13:31  乄一叶知秋  阅读(115)  评论(0编辑  收藏  举报