Python多线程编程

文章目录

    • 进程
      • 多进程
        • 多进程的问题
      • 进程的创建
      • 进程池
        • 什么是进程池
        • 进程池的创建
      • 进程锁
      • 进程的通信
    • 线程
      • threading模块
      • 线程对象------Thread
      • Thread 的生命周期
      • 多线程
      • 线程池的创建
      • 线程同步,锁
        • Lock(锁对象)
        • RLock(递归锁对象)
        • 条件对象-----Condition
        • 信号量对象-----Semaphore
        • 事件对象-----Event
        • 定时器对象-----Timer
        • 栅栏对象
      • 线程优先级队列( Queue)
      • 守护线程
        • 创建后台线程
    • GIL全局锁
    • 异步
      • gevent模块
      • Gevent协程对象的方法
      • async与await关键字
      • asyncio调用async函数

进程

进程就是程序执行的载体。
进程的应用:我们打开的每个软件、游戏,执行的每一个Python脚本都是启动一个进程。
每一个进程像人一样需要吃饭,它的粮食就是CPU和内存。

多进程

同时启动多个软件(进程),多个进程同时在执行程序,他们之间互不干扰,各自执行自己的业务逻辑。
Python多线程编程_第1张图片
多进程与并行的概念:
Python多线程编程_第2张图片

多进程的问题

通过进程模块执行的函数无法获取返回值。
多个进程同时修改文件可能会出现错误。
进程数量太多可能会导致资源不足,甚至死机的情况。

进程的创建

multiprocessing模块

函数名 功能 参数 返回值
Process 创建一个进程 target,args(元组) 进程对象
start 执行进程
join 阻塞程序
kill 杀死进程
is_alive 进程是否存活 bool

示例:

# coding:utf-8

import time
import os
import multiprocessing

def work_a():
    for i in range(10):
        print(i, 'a', os.getpid())
        time.sleep(1)

def work_b():
    for i in range(10):
        print(i, 'b', os.getpid())
        time.sleep(1)


if __name__ == '__main__':
    start = time.time() # 主进程1
    a_p = multiprocessing.Process(target=work_a)  # 子进程1
    # a_p.start()  # 子进程1执行
    # a_p.join()
    b_p = multiprocessing.Process(target=work_b)  # 子进程2
    # b_p.start()  # 子进程2执行

    for p in (a_p, b_p):
        p.start()

    # for p in (a_p, b_p):
        # p.join()

    for p in (a_p, b_p):
        print(p.is_alive())

    print('时间消耗是:', time.time() - start)  # 主进程代码2
    print('parent pid is %s' % os.getpid())  # 主进程代码3行

运行结果:work_a和word_b同时执行,执行完毕后才会执行下面的程序,如时间消耗等。
Python多线程编程_第3张图片
如果不加join的运行结果:先执行“时间消耗”,再执行子进程a和b。
Python多线程编程_第4张图片
如果先执行start work_a,然后阻塞join,再执行start work_b的效果

import time
import os
import multiprocessing

def work_a():
    for i in range(10):
        print(i, 'a', os.getpid())
        time.sleep(1)

def work_b():
    for i in range(10):
        print(i, 'b', os.getpid())
        time.sleep(1)

if __name__ == '__main__':
    start = time.time() # 主进程1
    a_p = multiprocessing.Process(target=work_a)  # 子进程1
    a_p.start()  # 子进程1执行
    a_p.join()
    b_p = multiprocessing.Process(target=work_b)  # 子进程2
    b_p.start()  # 子进程2执行
    print('时间消耗是:', time.time() - start)  # 主进程代码2
    print('parent pid is %s' % os.getpid())  # 主进程代码3行

Python多线程编程_第5张图片

进程池

什么是进程池

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,如果是上百个甚至上千个目标,收订的去创建进程的工作量巨大,此时就可以用到multiprocess模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求。

Python多线程编程_第6张图片

进程池的创建

multiprocessing模块

函数名 功能 参数 返回值
Pool 进程池创建 Processcount 进程池对象
apply_async 任务加入进程池(异步) func,args
join 等待进程池任务结束
close 关闭进程池

示例:

import os
import time
import multiprocessing

def work(count):
    print(count, os.getpid())
    time.sleep(5)
if __name__ == '__main__':
    pool = multiprocessing.Pool(5) #进程池中有5个进程
    for i in range(20):
        pool.apply_async(func=work, args=(i,))
    time.sleep(20)

运行结果:由结果看出,每个进程的id都不同,也有重复的,进程池中的进程反复使用,不会被关闭。会先执行5个,过了5s后再执行5个。
在这里插入图片描述

import os
import time
import multiprocessing

def work(count):
    print(count, os.getpid())
    time.sleep(5)
    return 'result is %s, pid is %s' % (count, os.getpid())

if __name__ == '__main__':
    pool = multiprocessing.Pool(5) #进程池中有5个进程
    results = []
    for i in range(20):
        result = pool.apply_async(func=work, args=(i,))
        results.append(result)
        
    for res in results:
        print(res.get())
    # pool.close()
    # pool.join()

结果:

0 9048
1 14480
2 13372
3 6764
4 1488
5 9048
result is 0, pid is 9048
6 14480
result is 1, pid is 14480
7 13372
result is 2, pid is 13372
8 6764
result is 3, pid is 6764
9 1488
result is 4, pid is 1488
10 9048
result is 5, pid is 9048
result is 6, pid is 14480
11 14480
12 13372
result is 7, pid is 13372
13 6764
result is 8, pid is 6764
14 1488
result is 9, pid is 1488
15 9048
result is 10, pid is 9048
16 14480
result is 11, pid is 14480
17 13372
result is 12, pid is 13372
18 6764
result is 13, pid is 6764
19 1488
result is 14, pid is 1488
result is 15, pid is 9048
result is 16, pid is 14480
result is 17, pid is 13372
result is 18, pid is 6764
result is 19, pid is 1488

进程锁

进程锁的加锁与解锁:

from multiprocessing import Process,Lock
manage = Manager()
lock = manage.Lock()
函数名 功能 参数 返回值
acquire 上锁
release 开锁(解锁)

示例:

# coding:utf-8

import os
import time
import multiprocessing


def work(count, lock):
    lock.acquire()
    print(count, os.getpid())
    time.sleep(5)
    lock.release()
    return 'result is %s, pid is %s' % (count, os.getpid())


if __name__ == '__main__':
    pool = multiprocessing.Pool(5) #进程池中有5个进程
    manger = multiprocessing.Manager()
    lock = manger.Lock()
    results = []
    for i in range(20):
        result = pool.apply_async(func=work, args=(i, lock))
        # results.append(result)

    # for res in results:
    #     print(res.get())
    pool.close()
    pool.join()

结果发现每次只有一个进程在执行。

进程的通信

队列的创建 multiprocessing

函数名 功能 参数 返回值
Queue 队列的创建 mac_cout 队列对象
put 信息放入队列 message
get 获取队列信息 str

示例:

import time
import json
import multiprocessing

class Work(object):
    def __init__(self, q):
        self.q = q

    def send(self, message):
        if not isinstance(message, str):
            message = json.dumps(message)
        self.q.put(message)
        
    def receive(self):
        while 1:
            result = self.q.get()
            try:
                res = json.loads(result)
            except:
                res = result
            print('recv is %s' % res)


if __name__ == '__main__':
    q = multiprocessing.Queue()
    work = Work(q)
    send = multiprocessing.Process(target=work.send, args=({'name': '小慕'},))
    recv = multiprocessing.Process(target=work.receive)
    
    send.start()
    recv.start()

结果:recv is {'name': '小慕'}
但是程序并没有退出,这是因为程序并不知道该何时退出。在末尾加上recv.terminate()即可。

# coding:utf-8

import time
import json
import multiprocessing

class Work(object):
    def __init__(self, q):
        self.q = q

    def send(self, message):
        if not isinstance(message, str):
            message = json.dumps(message)
        self.q.put(message)

    def send_all(self):
        for i in range(20):
            self.q.put(i)
            time.sleep(1)

    def receive(self):
        while 1:
            result = self.q.get()
            try:
                res = json.loads(result)
            except:
                res = result
            print('recv is %s' % res)


if __name__ == '__main__':
    q = multiprocessing.Queue()
    work = Work(q)
    send = multiprocessing.Process(target=work.send, args=({'name': '小慕'},))
    recv = multiprocessing.Process(target=work.receive)
    send_all_p = multiprocessing.Process(target=work.send_all)

    send_all_p.start()
    send.start()
    recv.start()

    send_all_p.join()
    recv.terminate()

Python多线程编程_第7张图片

线程

线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
进程提供线程执行程序的前置要求,线程在重组的资源配备下,去执行程序。

Python3 线程中常用的两个模块为:

  • _thread
  • threading(推荐使用)

thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 “_thread”。

threading模块

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

  • threading.currentThread():返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
  • threading.get_ident():返回当前线程的 “线程标识符”。它是一个非零的整数。它的值没有直接含义,主要是用作 magic cookie,比如作为含有线程相关数据的字典的索引。线程标识符可能会在线程退出,新线程创建时被复用。
  • threading.main_thread():返回主 Thread 对象。一般情况下,主线程是Python解释器开始时创建的线程。
  • threading.settrace(func):为所有 threading 模块开始的线程设置追踪函数。在每个线程的 run() 方法被调用前,func 会被传递给 sys.settrace() 。
  • threading.setprofile(func):为所有 threading 模块开始的线程设置性能测试函数。在每个线程的 run()方法被调用前,func 会被传递给 sys.setprofile() 。

线程对象------Thread

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

调用这个构造函数时,必需带有关键字参数。参数如下:

  • group 应该为 None;为了日后扩展 ThreadGroup 类实现而保留。
  • target 是用于 run() 方法调用的可调用对象。默认是 None,表示不需要调用任何方法。
  • name 是线程名称。默认情况下,由 “Thread-N” 格式构成一个唯一的名称,其中 N 是小的十进制数。
  • args 是用于调用目标函数的参数元组。默认是 ()。
  • kwargs 是用于调用目标函数的关键字参数字典。默认是 {}

Thread类的方法:

方法名 说明 用法
start 启动线程
它在一个线程里最多只能被调用一次。它安排对象的 run() 方法在一个独立的控制进程中调用。
start()
run 线程活动 run()
join 阻塞直到线程执行结束
这会阻塞调用这个方法的线程,直到被调用 join() 的线程终结 – 不管是正常终结还是抛出未处理异常 – 或者直到发生超时,超时选项是可选的。
一定要在 join() 后调用 is_alive() 才能判断是否发生超时 – 如果线程仍然存活,则 join() 超时。
一个线程可以被 join() 很多次
join(timeout=None)
getName 获取线程的名字 getName()
setName 设置线程的名字 setName(name)
is_alive 判读线程是否存活 is_alive()
setDaemon 守护线程 setDaemon(True)

创建 Thread 对象有 2 种手段。
1.直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
线程名字:thread = threading.Thread(target=test,name='TestThread')

import threading
import time

def test():

    for i in range(5):
        print('test ',i)
        time.sleep(1)


thread = threading.Thread(target=test)
thread.start()

for i in range(5):
    print('main ', i)
    time.sleep(1)

运行结果如下:在主线程上打印5次,在子线程上打印5次。

test  0
main  0
main  1
test  1
main  2
test  2
main  3
test  3
main  4
test  4

2.编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。

import threading
import time


class TestThread(threading.Thread):

    def __init__(self,name=None):
        threading.Thread.__init__(self,name=name)

    def run(self):
        for i in range(5):
            print(threading.current_thread().name + ' test ', i)
            time.sleep(1)


thread = TestThread(name='TestThread')
thread.start()


for i in range(5):
    print(threading.current_thread().name+' main ', i)
    print(thread.name+' is alive ', thread.isAlive())
    time.sleep(1)

线程的问题:
通过线程执行的函数无法获取返回值。
多个线程同时修改文件可能造成数据错乱。

Thread 的生命周期

  • 创建对象时,代表 Thread 内部被初始化。
  • 调用 start() 方法后,thread 会开始运行。
  • thread 代码正常运行结束或者是遇到异常,线程会终止。

如果要达到,MainThread 结束,子线程也立马结束:
只需要在子线程调用 start() 方法之前设置 daemon 就好了。
也可以在子线程的构造器中传递 daemon 的值为 True。

多线程

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

  • 使用线程可以把占据长时间的程序中的任务放到后台去处理。
  • 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
  • 程序的运行速度可能加快。
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

线程可以分为:

  • 内核线程:由操作系统内核创建和撤销。
  • 用户线程:不需要内核支持而在用户程序中实现的线程。

创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
示例:

import time
import random
import threading

lists = ['python', 'django', 'tornado',
         'flask', 'bs5', 'requests', 'uvloop'
]

new_lists = []


def work():
    if len(lists) == 0:
        return
    data = random.choice(lists)
    lists.remove(data)
    new_data = '%s_new' % data
    new_lists.append(new_data)
    time.sleep(1)


if __name__ == '__main__':
    start = time.time()
    for i in range(len(lists)):
        work()
    print('old list:', lists)
    print('new list:', new_lists)
    print('time is %s' % (time.time() - start))

在这里插入图片描述
使用多线程的方式处理:

# coding:utf-8

import time
import random
import threading

lists = ['python', 'django', 'tornado',
         'flask', 'bs5', 'requests', 'uvloop'
]

new_lists = []


def work():
    if len(lists) == 0:
        return
    data = random.choice(lists)
    lists.remove(data)
    new_data = '%s_new' % data
    new_lists.append(new_data)
    time.sleep(1)


if __name__ == '__main__':
    start = time.time()
    # print('old list len:', len(lists))
    t_list = []
    for i in range(len(lists)):
        t = threading.Thread(target=work)
        t_list.append(t)
        t.start()

    for t in t_list:
        t.join()

    print('old list:', lists)
    print('new list:', new_lists)
    # print('new_list len', len(new_lists))
    print('time is %s' % (time.time() - start))

在这里插入图片描述
时间只用了1s,没用线程之前是7s。

线程池的创建

concurrent包

方法名 说明 用法
futures.ThreadPoolExecutor 创建线程池 tpool = ThreadPoolExecutor(max_workers)
submit 往线程池中加任务 submit(target, args)
done 线程池中的某个线程是否完成了任务 done()
result 获取当前线程执行任务的结果 result(name)

示例:

# coding:utf-8

import time
import os
import threading
from concurrent.futures import ThreadPoolExecutor

def work(i):
    print(i)
    time.sleep(1)

if __name__ == '__main__': 
    t = ThreadPoolExecutor(2)
    for i in range(10):
        t.submit(work, (i, ))

在这里插入图片描述
每2个执行一次。

# coding:utf-8

import time
import os
import threading

from concurrent.futures import ThreadPoolExecutor


lock = threading.Lock()


def work(i):
    lock.acquire()
    print(i, os.getpid())
    time.sleep(1)
    lock.release()
    # return 'result %s' % i


if __name__ == '__main__':
    t = ThreadPoolExecutor(2)
    for i in range(20):
        t.submit(work, (i, ))   

在这里插入图片描述
加锁后,每次执行1个。

线程同步,锁

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步。

Lock(锁对象)

请求锁定 — 进入锁定池等待 — — 获取锁 — 已锁定— — 释放锁
Lock包含两种状态——锁定和非锁定。
方法:

  • acquire(blocking=True, timeout=-1): 使线程进入同步阻塞状态,尝试获得锁定。
    当调用时参数 blocking 设置为 True (缺省值),阻塞直到锁被释放,然后将锁锁定并返回 True 。
    在参数 blocking 被设置为 False 的情况下调用,将不会发生阻塞。如果调用时 blocking 设为 True 会阻塞,并立即返回 False ;否则,将锁锁定并返回 True。
  • release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。

当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError 异常。

对于Lock对象而言,如果一个线程连续两次release,使得线程死锁。所以Lock不常用,一般采用Rlock进行线程锁的设定。

RLock(递归锁对象)

RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。
构造方法:mylock = Threading.RLock()
实例方法:

  • acquire(blocking=True, timeout=-1)
    可以阻塞或非阻塞地获得锁
    当无参数调用时: 如果这个线程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。一旦锁被解锁(不属于任何线程),则抢夺所有权,设置递归等级为一,并返回。如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权。在这种情况下,没有返回值。
  • release()
    释放锁,自减递归等级。如果减到零,则将锁重置为非锁定状态(不被任何线程拥有),并且,如果其他线程正被阻塞着等待锁被解锁,则仅允许其中一个线程继续。如果自减后,递归等级仍然不是零,则锁保持锁定,仍由调用线程拥有。

示例:

import threading
mylock = threading.RLock()
num = 0
class WorkThread(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.t_name = name
    def run(self):
        global num
        while True:
            mylock.acquire()
            print('\n%s locked, number: %d' % (self.t_name, num))
            if num >= 2:
                mylock.release()
                print('\n%s released, number: %d' % (self.t_name, num))
                break
            num += 1
            print('\n%s released, number: %d' % (self.t_name, num))
            mylock.release()
def test():
    thread1 = WorkThread('A-Worker')
    thread2 = WorkThread('B-Worker')
    thread1.start()
    thread2.start()
if __name__ == '__main__':
    test() 

结果:

A-Worker locked, number: 0

A-Worker released, number: 1

A-Worker locked, number: 1

A-Worker released, number: 2

A-Worker locked, number: 2

A-Worker released, number: 2

B-Worker locked, number: 2

B-Worker released, number: 2

注意:如果使用RLock,那么acquire和release必须成对出现。

条件对象-----Condition

条件变量总是与某种类型的锁对象相关联,锁对象可以通过传入获得,或者在缺省的情况下自动创建。当多个条件变量需要共享同一个锁时,传入一个锁很有用。锁是条件对象的一部分,你不必单独地跟踪它。

class threading.Condition(lock=None)
实现条件变量对象的类。一个条件变量对象允许一个或多个线程在被其它线程所通知之前进行等待。

参数:如果给出了非 None 的 lock 参数,则它必须为 Lock 或者 RLock 对象,并且它将被用作底层锁。否则,将会创建新的 RLock 对象,并将其用作底层锁。
方法:

  • acquire(*args) 请求底层锁。此方法调用底层锁的相应方法,返回值是底层锁相应方法的返回值。
  • release() 释放底层锁。此方法调用底层锁的相应方法。没有返回值。
  • wait(timeout=None)
    等待直到被通知或发生超时。如果线程在调用此方法时没有获得锁,将会引发 RuntimeError 异常。
  • wait_for(predicate, timeout=None)
    等待,直到条件计算为真。 predicate 应该是一个可调用对象而且它的返回值可被解释为一个布尔值。可以提供 timeout 参数给出最大等待时间。
  • notify(n=1) 默认唤醒一个等待这个条件的线程。如果调用线程在没有获得锁的情况下调用这个方法,会引发 RuntimeError异常。
    这个方法唤醒最多 n 个正在等待这个条件变量的线程;如果没有线程在等待,这是一个空操作。
  • notify_all()
    唤醒所有正在等待这个条件的线程。这个方法行为与 notify() 相似,但并不只唤醒单一线程,而是唤醒所有等待线程。如果调用线程在调用这个方法时没有获得锁,会引发 RuntimeError 异常。
信号量对象-----Semaphore

信号量通常用于保护数量有限的资源,例如数据库服务器。在资源数量固定的任何情况下,都应该使用有界信号量。在生成任何工作线程前,应该在主线程中初始化信号量。

class threading.Semaphore(value=1)

该类实现信号量对象。信号量对象管理一个原子性的计数器,代表 release() 方法的调用次数减去 acquire() 的调用次数再加上一个初始值。如果需要, acquire() 方法将会阻塞直到可以返回而不会使得计数器变成负数。在没有显式给出 value 的值时,默认为1。

可选参数 value 赋予内部计数器初始值,默认值为 1 。如果 value 被赋予小于0的值,将会引发 ValueError 异常。

  • acquire(blocking=True, timeout=None)
    获取一个信号量。
  • release()
    释放一个信号量,将内部计数器的值增加1。当计数器原先的值为0且有其它线程正在等待它再次大于0时,唤醒正在等待的线程。
    示例:
# 在生成任何工作线程前,应该在主线程中初始化信号量
maxconnections = 5
# ...
pool_sema = BoundedSemaphore(value=maxconnections)


# 工作线程生成后,当需要连接服务器时,这些线程将调用信号量的 acquire 和 release 方法:
with pool_sema:
    conn = connectdb()
    try:
        # ... use connection ...
    finally:
        conn.close()

有界信号量:

class threading.BoundedSemaphore(value=1)

该类实现有界信号量。有界信号量通过检查以确保它当前的值不会超过初始值。如果超过了初始值,将会引发 ValueError 异常。在大多情况下,信号量用于保护数量有限的资源。如果信号量被释放的次数过多,则表明出现了错误。没有指定时, value 的值默认为1。

事件对象-----Event

这是线程之间通信的最简单机制之一:一个线程发出事件信号,而其他线程等待该信号。

class threading.Event

实现事件对象的类。事件对象管理一个内部标志,调用 set() 方法可将其设置为true。调用 clear() 方法可将其设置为false。调用 wait() 方法将进入阻塞直到标志为true。这个标志初始时为false。

  • is_set()
    Return true if and only if the internal flag is true.
  • set()
    将内部标志设置为true。所有正在等待这个事件的线程将被唤醒。当标志为true时,调用 wait() 方法的线程不会被被阻塞。
  • clear()
    将内部标志设置为false。之后调用 wait() 方法的线程将会被阻塞,直到调用 set() 方法将内部标志再次设置为true。
  • wait(timeout=None)
    阻塞线程直到内部变量为true。如果调用时内部标志为true,将立即返回。否则将阻塞线程,直到调用 set() 方法将标志设置为true或者发生可选的超时。
定时器对象-----Timer

此类表示一个操作应该在等待一定的时间之后运行 — 相当于一个定时器。 Timer 类是 Thread 类的子类,因此可以像一个自定义线程一样工作。

class threading.Timer(interval, function, args=None, kwargs=None)

创建一个定时器,在经过 interval 秒的间隔事件后,将会用参数 args 和关键字参数 kwargs 调用 function。如果 args 为 None (默认值),则会使用一个空列表。如果 kwargs 为 None (默认值),则会使用一个空字典。

  • cancel()
    停止定时器并取消执行计时器将要执行的操作。仅当计时器仍处于等待状态时有效

调用 start() 方法启动定时器。而 cancel() 方法可以停止计时器(在计时结束前), 定时器在执行其操作之前等待的时间间隔可能与用户指定的时间间隔不完全相同。

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

t = Timer(30.0, hello)
t.start()  # after 30 seconds, "hello, world" will be printed
栅栏对象

栅栏类提供一个简单的同步原语,用于应对固定数量的线程需要彼此相互等待的情况。线程调用 wait() 方法后将阻塞,直到所有线程都调用了 wait() 方法。此时所有线程将被同时释放。
栅栏对象可以被多次使用,但进程的数量不能改变。

class threading.Barrier(parties, action=None, timeout=None)

创建一个需要 parties 个线程的栅栏对象。如果提供了可调用的 action 参数,它会在所有线程被释放时在其中一个线程中自动调用。 timeout 是默认的超时时间,如果没有在 wait() 方法中指定超时时间的话。

  • wait(timeout=None)
    冲出栅栏。当栅栏中所有线程都已经调用了这个函数,它们将同时被释放。如果提供了 timeout 参数,这里的 timeout 参数优先于创建栅栏对象时提供的 timeout 参数。
  • reset()
    重置栅栏为默认的初始态。如果栅栏中仍有线程等待释放,这些线程将会收到 BrokenBarrierError 异常。
  • abort()
    使栅栏进入破损态。这将导致所有已经调用和未来调用的 wait() 方法中引发 BrokenBarrierError 异常。使用这个方法的一种情况是需要中止程序以避免死锁。
  • parties
    冲出栅栏所需要的线程数量。
  • n_waiting
    当前时刻正在栅栏中阻塞的线程数量。
  • broken
    一个布尔值,值为 True 表明栅栏为破损态。
    示例:使用简便的方法实现客户端进程与服务端进程同步
b = Barrier(2, timeout=5)

def server():
    start_server()
    b.wait()
    while True:
        connection = accept_connection()
        process_server_connection(connection)

def client():
    b.wait()
    while True:
        connection = make_connection()
        process_client_connection(connection)

线程优先级队列( Queue)

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。

这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。

Queue 模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,
  • Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作

示例:

#!/usr/bin/python3

import queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print ("开启线程:" + self.name)
        process_data(self.name, self.q)
        print ("退出线程:" + self.name)

def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print ("%s processing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待队列清空
while not workQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")

结果:

开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出线程:Thread-3
退出线程:Thread-2
退出线程:Thread-1
退出主线程

守护线程

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。Python 解释器的垃圾回收线程就是典型的后台线程。如果所有的前台线程都死亡了,那么后台线程会自动死亡。

创建后台线程

创建后台线程有两种方式:

  1. 主动将线程的 daemon 属性设置为 True。
  2. 后台线程启动的线程默认是后台线程。

daemon:一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False。
示例:

import threading
# 定义后台线程的线程执行体与普通线程没有任何区别
def action(max):
    for i in range(max):
        print(threading.current_thread().name + "  " + str(i))
t = threading.Thread(target=action, args=(100,), name='后台线程')
# 将此线程设置成后台线程
# 也可在创建Thread对象时通过daemon参数将其设为后台线程
t.daemon = True
# 启动后台线程
t.start()
for i in range(10):
    print(threading.current_thread().name + "  " + str(i))
# -----程序执行到此处,前台线程(主线程)结束------
# 后台线程也应该随之结束

GIL全局锁

GIL的作用:单一CPU工作,线程安全,pypy,多进程+多线程

异步

异步是轻量级的线程,又称协程。
异步可以获取异步函数的返回值。主进程需要异步才能使用。

gevent模块

安装:pip install gevent
Micorsoft Visual C++
pip install wheel

函数名 功能 参数 返回值
spawn 创建协程对象 Func,args 协程对象
joinall 批量处理协程对象 [spawnobj] [spawnobj]

Gevent协程对象的方法

函数名 功能 参数 返回值
get(value) 获取异步程序结果 函数的返回值
join 阻塞等待异步程序结束
kill 杀死当前协程
dead 判断协程是否消亡 bool

async与await关键字

async定义异步

async def test():
	return 'a'

await执行异步

async def handle():
	result = await test()

asyncio调用async函数

函数名 功能 参数 返回值
gather 将异步函数批量执行 asyncfunc... List 函数的返回结果
run 执行主异步函数 [task] 执行函数的返回结果

示例:

async def main():
	result = await asyncio.gather(
			a(),
			b()
	)
	print(result)


if __name__ = '__main__':
	asyncio.run(main())

https://docs.python.org/zh-cn/3.6/library/threading.html?highlight=%E7%BA%BF%E7%A8%8B

你可能感兴趣的:(python,python,线程,进程,锁)