★概念
进程是由若干线程组成的,一个进程至少有一个线程;
线程是CPU调度的最小单位;
线程之间资源共享。
★全局解释器锁(GIL)
cpython的特性
同一时刻只能有一个线程访问CPU;
锁的是线程
在多线程环境中,Python 虚拟机按以下方式执行:
1,设置GIL;
2,切换到一个线程去执行;
3,运行指定数量字节码指令或线程主动让出控制;
4,把线程设置为睡眠状态;
5,解锁 GIL;
6,再次重复以上所有步骤。
★threading模块
◇线程的创建-threading.Thread
1 import time
2 import os
3 from threading import Thread
4
5 """多线程并发"""
6
7
8 def func(n):
9 time.sleep(1)
10 print('in func%s' % n)
11 print('子进程PID:%s' % os.getpid())
12
13
14 for i in range(10):
15 t = Thread(target=func, args=(i+1,)) # 并发10个线程
16 t.start()
17
18 print('主进程PID:%s' % os.getpid())
多线程的创建.py
1 import time
2 from threading import Thread
3
4
5 class MyThread(Thread): # 继承线程类
6 def __init__(self, arg): # 初始化MyThread的参数
7 super().__init__()
8 self.arg = arg
9
10 def run(self): # 重写run方法
11 time.sleep(1)
12 print('in func%s' % self.arg)
13
14
15 for i in range(10):
16 t = MyThread(i+1)
17 t.start()
通过面向对象实现多线程.py
1 import os
2 from threading import Thread
3
4
5 def func(a, b):
6 global g # 声明全局变量,主线程和子线程共享g
7 c = a + b
8 g = 0 # 这个g同时对主线程的g有作用
9 print('in func的结果:%s' % c)
10
11
12 g = 100
13 t_lis = []
14 for i in range(10):
15 t = Thread(target=func, args=(i+1, 5))
16 t.start()
17 t_lis.append(t)
18
19 for t in t_lis:
20 t.join()
21
22 print('主线程g的值:', g) # g的值已被子线程修改
23 print('主进程PID:%s' % os.getpid())
多线程之间的数据共享.py
守护线程
守护线程会在主线程代码结束之后等待【其他子线程】的结束而结束
与进程相同:
会等待子线程结束才结束
会等待子进程结束才结束
与进程不同:
▽守护线程在【其他线程结束】才结束 -- >这时候主线程已经结束
▽守护进程在【代码结束】而结束 --> 这时候主进程还没结束
1 from threading import Thread
2 import time
3
4
5 def func1():
6 while True:
7 print('**********')
8 time.sleep(1)
9
10
11 def func2():
12 time.sleep(10)
13 print('in func2')
14
15
16 t = Thread(target=func1,)
17 t.daemon = True # 设置守护线程, 会一直等所有子线程执行结束,守护线程才结束
18 t.start()
19 t1 = Thread(target=func2,)
20 t1.start()
21 # t1.join()
22 print('in main thread')
守护线程的简单例子.py
◇线程的锁-threading.Lock
方法
acquire() 上锁
release() 解锁
Lock() 互斥锁
有两个互斥锁时,会有可能产生死锁问题
1 from threading import Thread, Lock
2 import time
3
4 """互斥锁"""
5
6
7 def func(lock):
8 lock.acquire() # 一个线程执行完了,下一个线程才能进去执行
9 global n
10 temp = n
11 time.sleep(0.2)
12 n = temp - 1
13 lock.release()
14
15
16 n = 10
17 lock = Lock()
18 t_lst = []
19 for i in range(10):
20 t = Thread(target=func, args=(lock,))
21 t_lst.append(t)
22 t.start()
23
24 for t in t_lst:
25 t.join()
26 print(n)
加锁保证多线程的数据安全.py
执行完这个线程,2,(这时候第一个执行的线程执行完)CPU执行下一个线程,3,再下一个线程重复以上操作
第一个执行的线程还没有执行完),3,再下一个线程重复以上操作 -->大家共享阻塞的时间(相当于同时等待)
1 from threading import Thread, Lock
2 import time
3
4
5 def eat1(name):
6 noodle_lock.acquire()
7 print('%s拿到了面条' % name)
8 fork_lock.acquire()
9 print('%s拿到了叉子' % name)
10 print('%s 吃面了' % name)
11 noodle_lock.release()
12 fork_lock.release()
13
14
15 def eat2(name):
16 fork_lock.acquire()
17 print('%s拿到了叉子' % name)
18 time.sleep(0.1) # 先阻塞一下(迫使时间片切换),让另一个线程去拿了面条的锁,然后就会产生死锁的问题
19 noodle_lock.acquire() # 其他线程拿了锁没有解锁,导致我这里无法拿到而一直等待,
20 print('%s拿到了面条' % name)
21 print('%s 吃面了' % name)
22 noodle_lock.release()
23 fork_lock.release()
24
25
26 noodle_lock = Lock()
27 fork_lock = Lock()
28 Thread(target=eat1, args=('alex', )).start()
29 Thread(target=eat2, args=('jin', )).start()
30 Thread(target=eat1, args=('boss', )).start()
31 Thread(target=eat2, args=('egg', )).start()
死锁问题.py
RLock() 递归锁
1,在一个线程能有多个锁
2,为了解决死锁问题
1 from threading import RLock
2
3 rlock = RLock()
4
5 rlock.acquire()
6 rlock.acquire()
7 rlock.acquire()
8 print('hello')
简单递归锁.py
1 from threading import Thread, RLock
2 import time
3
4
5 def eat1(name):
6 noodle_lock.acquire()
7 print('%s拿到了面条' % name)
8 fork_lock.acquire()
9 print('%s拿到了叉子' % name)
10 print('%s 吃面了' % name)
11 noodle_lock.release()
12 fork_lock.release()
13
14
15 def eat2(name):
16 fork_lock.acquire() # 上锁了
17 print('%s拿到了叉子' % name)
18 time.sleep(2) # 阻塞,就算切换到其他线程也无法执行,因为是同一把锁,另外的线程外面也有锁
19 noodle_lock.acquire()
20 print('%s拿到了面条' % name)
21 print('%s 吃面了' % name)
22 noodle_lock.release()
23 fork_lock.release() # 已经完全解锁了,其他线程能去上锁了然后去执行
24
25
26 noodle_lock = fork_lock = RLock()
27 Thread(target=eat1, args=('alex', )).start()
28 Thread(target=eat2, args=('jin', )).start()
29 Thread(target=eat1, args=('boss', )).start()
30 Thread(target=eat2, args=('egg', )).start()
通过递归锁解决死锁问题.py
◇信号量-threading.Semaphore
方法
acquire() 上锁
release() 解锁
同一时间只有n个线程可以执行上锁代码
1 from threading import Semaphore, Thread
2 import time
3
4
5 def func(n):
6 sem.acquire()
7 time.sleep(1)
8 print(n)
9 sem.release()
10
11
12 sem = Semaphore(3) # 设置一个信号量, 只能有3个线程可以并发执行
13 for i in range(10):
14 t = Thread(target=func, args=(i,)) # 在线程里sem不用传进来,子线程共享主线程的变量
15 t.start()
线程信号量的实例.py
◇事件-threading.Event
状态
False (默认) wait()阻塞
True wait()非阻塞
修改状态
set 设置为True
clear 设置为False
wait方法
只等待1s就往下执行
1 from threading import Event, Thread
2 import time
3 import random
4
5
6 def connect_db():
7 count = 0
8 while count < 3:
9 e.wait(1) # 只阻塞1S,往下执行
10 if e.is_set():
11 print('连接数据库成功!!!')
12 break
13 else:
14 count += 1
15 print('第%s次连接失败' % count)
16 else:
17 raise TimeoutError('连接超时!!!')
18
19
20 def check_web():
21 time.sleep(random.randint(0, 3)) # 模拟网络延迟
22 e.set()
23
24
25 e = Event()
26 t1 = Thread(target=connect_db) # 直到事件状态被设置为True,这个线程才能连接上数据库
27 t2 = Thread(target=check_web) # 这个线程负责修改事件状态
28 t1.start()
29 t2.start()
模拟数据库登录.py
◇条件-threading.Condition
使得线程等待,只有满足某条件时,才释放n个线程
方法
acquire()
release()
notify() 造钥匙(一次性钥匙)
wait() 等钥匙
1 from threading import Condition, Thread
2
3
4 def func(i):
5 con.acquire()
6 con.wait() # 等造钥匙出来
7 print('在%s循环里' % i)
8 con.release()
9
10
11 con = Condition()
12 for i in range(10):
13 t = Thread(target=func, args=(i, ))
14 t.start()
15 while True:
16 num = int(input('>>>'))
17 con.acquire()
18 con.notify(num) # 造钥匙(一次性钥匙)
19 con.release()
条件的简单例子.py
◇定时器-threading.Timer
指定n秒后执行某个线程
1 from threading import Timer
2 import time
3
4
5 def func():
6 print('时间同步')
7
8
9 while True:
10 Timer(2, func).start() # 实例化一个定时器
11 time.sleep(2) # 等待2S,再重新进入循环
定时器的简单例子.py
★队列【数据安全】-queue
◇先进先出-queue.Queue
方法
- put()
- get()
- put_nowait() 满值不阻塞,但报错
- get_nowait() 没值不阻塞,但报错
1 q = queue.Queue()
2 q.put('a')
3 q.put('b')
4 q.put('c')
5 print(q.get())
6 print('ttt:%s' % q.get_nowait())
7 print('ttt:%s' % q.get_nowait())
8 print('ttt:%s' % q.get_nowait()) # 队列已空,不阻塞,但报错
先进先出队列.py
◇先进后出-queue.LifoQueue(栈)
方法
- put()
- get()
- put_nowait() 满值不阻塞,但报错
- get_nowait() 没值不阻塞,但报错
1 q = queue.LifoQueue()
2 q.put('a')
3 q.put('b')
4 q.put('c')
5 print(q.get())
6 print('ttt:%s' % q.get_nowait())
7 print('ttt:%s' % q.get_nowait())
8 print('ttt:%s' % q.get_nowait()) # 队列已空,不阻塞,但报错
先进后出队列.py
◇优先级队列-queue.PriorityQueue
1, 数字越低,优先级越高;
2, 数字相同,ASCII越小,优先级越高
3, 返回的是一个元祖
方法
- put()
- get()
- put_nowait() 满值不阻塞,但报错
- get_nowait() 没值不阻塞,但报错
1 q = queue.PriorityQueue()
2 q.put((30, 'c'))
3 q.put((10, 'b'))
4 q.put((10, 'j'))
5 q.put((20, 'a'))
6 q.put((20, 'z'))
7
8 print(q.get()) # 1, 数字越低,优先级越高;2, 数字相同,ASCII越小,优先级越高
9 print(q.get())
10 print(q.get_nowait())
11 print(q.get_nowait())
12 print(q.get_nowait())
13 print(q.get_nowait()) # 队列已空,报错
优先级队列.py
★标准模块-concurrent.futures
提供了高度封装的异步调用接口
子模块
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
方法
submit 异步提交任务
map for循环submit操作
shutdown 相当于进程池pool.close() + pool.join()
result 取得结果
add_done_callbak 回调函数
1 import time
2 from concurrent.futures import ThreadPoolExecutor
3
4
5 def func(n):
6 time.sleep(1)
7 print(n)
8 return n * n
9
10
11 tpool = ThreadPoolExecutor(max_workers=5) # 实例化一个线程池,可以同时并发5个线程
12 tpool.map(func, range(20)) # 1,for循环submit操作;2,没有返回值
通过map并发线程池.py
1 import time
2 from concurrent.futures import ThreadPoolExecutor
3
4
5 def func(n):
6 time.sleep(1)
7 print(n)
8 return n * n
9
10
11 tpool = ThreadPoolExecutor(max_workers=5)
12 t_lst = []
13 for i in range(20):
14 t = tpool.submit(func, i) # 异步提交任务,并且有【返回对象】
15 t_lst.append(t) # 把返回对象添加到一个列表里
16
17 tpool.shutdown() # close+join,如果不加这个效率会高一点,一边在子线程把返回对象添加到列表里(接下)
18 print('主线程') # (接上)一边在主线程里循环读取返回对象的结果
19
20 for t in t_lst:
21 print('return:', t.result()) # result 获取结果
线程池.py
1 import time
2 from concurrent.futures import ThreadPoolExecutor
3
4
5 def func(n):
6 time.sleep(1)
7 print(n)
8 return n * n
9
10
11 def call_back(m):
12 print('回调函数的结果是%s:' % m.result())
13
14
15 tpool = ThreadPoolExecutor(max_workers=5)
16 for i in range(20):
17 tpool.submit(func, i).add_done_callback(call_back) # 回调函数
回调函数的例子.py
进程的用法相同,只需要把子模块名字改一下即可