中文文档
一个进程包含一个或多个线程
Thread
类
Thread
类表示在单独的控制线程中运行的活动。有两种方法来指定活动:通过将可调用对象传递给构造函数,或者通过重写子类中的run()
方法。
普通
import time
def show():
print("咕嘿嘿~")
time.sleep(1)
if __name__ == '__main__':
sr = time.time()
for i in range(5):
show()
ed = time.time()
zw = ed - sr
print("耗时 %.3f" % zw)
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 05-线程-threading.py
咕嘿嘿~
咕嘿嘿~
咕嘿嘿~
咕嘿嘿~
咕嘿嘿~
耗时 5.006
加入线程
import threading
import time
def show():
print("咕嘿嘿~")
time.sleep(1)
if __name__ == '__main__':
sr = time.time()
for i in range(5): # 5 个线程
t = threading.Thread(target=show)
t.start()
ed = time.time()
zw = ed - sr
print("耗时 %.3f" % zw)
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 05-线程-threading.py
咕嘿嘿~
咕嘿嘿~
咕嘿嘿~
咕嘿嘿~
咕嘿嘿~
耗时 0.002
主线程等待所有子线程结束后才停止运行,emmm,是为了给子线程收尸
如果多个线程执行的都是同一个函数,各自之间是不会有影响的,各用各地。
Thread
子类
如果子类覆盖了构造函数,它必须在对线程做任何其他事情之前调用基类构造函数(
Thread.__init__()
)
import threading
import time
class NewThread(threading.Thread): # 与创建多进程的子类差不多
def __init__(self):
threading.Thread.__init__(self)
def run(self):
print("咕嘿嘿~%s" % self.name)
time.sleep(1)
if __name__ == '__main__':
sr = time.time()
for i in range(4):
t = NewThread()
t.start()
ed = time.time()
zw = ed - sr
print("共耗时: %.3f" % zw)
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 06-线程-threading-子类.py
咕嘿嘿~Thread-1
咕嘿嘿~Thread-2
咕嘿嘿~Thread-3
咕嘿嘿~Thread-4
共耗时: 0.001
共享全局变量
进程中的资源数据都是互不影响的,线程之间共享全局变量
import threading
import time
num = 10
def addNum():
global num
num += 1
print("线程1--num = %d" % num)
def showNum():
global num
print("线程2--num = %d" % num)
if __name__ == '__main__':
print("线程创建前:num = %d" % num)
t1 = threading.Thread(target=addNum)
t1.start()
time.sleep(1)
t2 = threading.Thread(target=showNum)
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 07-线程-全局变量共享.py
线程创建前:num = 10
线程1--num = 11
线程2--num = 11
线程都在同一个进程里,全局变量也在这个进程里,所以同一进程里的线程之间共享全局变量
所以就没有进程间的通信那么麻烦,都是共享的
线程共享全局变量的问题
import threading
import time
num = 0
def addNum():
global num
for i in range(100000):
num += 1
print("线程1--num = %d" % num)
def showNum():
global num
for i in range(100000):
num += 1
print("线程2--num = %d" % num)
if __name__ == '__main__':
print("线程创建前:num = %d" % num)
t1 = threading.Thread(target=addNum)
t1.start()
# time.sleep(1) # 注释掉
t2 = threading.Thread(target=showNum)
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 08-线程-全局变量共享的问题.py
线程创建前:num = 0
线程1--num = 138376
线程2--num = 150962
两个线程都加 100000 理应出现一个 200000,但却一个也没有
将列表当做参数传递
import threading
import time
num = 0
def addNum(nums):
nums.append(4)
print("线程1--num = ", nums)
def showNum(nums):
time.sleep(1) # 等待1s,保证线程1完成
print("线程2--num = ", nums)
if __name__ == '__main__':
num = [1, 2, 3]
t1 = threading.Thread(target=addNum, args=(num,))
t1.start()
t2 = threading.Thread(target=showNum, args=(num,))
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 09-线程-列表传递.py
线程1--num = [1, 2, 3, 4]
线程2--num = [1, 2, 3, 4]
在使用多线程的时候,数据共享是方便,但要确定结果是不是自己想要的
为什么上一个程序加了两次 100000,并没有得到 200000
操作系统不一定会将一条语句彻底执行完在执行下一条:
num += 1
,可能先执行完线程一的+=1
后,再执行线程二的+=1
,再执行线程一的num = 1
,再线程二的num = 1
,那么全局变量就还是1
,得不到预想的结果
解决方法1 -- 轮询
import threading
import time
num = 0
flage = 1
def addNum():
global num
global flage
if flage == 1:
for i in range(100000):
num += 1
flage = 0
print("线程1--num = %d" % num)
def showNum():
global num
while True:
if flage == 0:
for i in range(100000):
num += 1
break
print("线程2--num = %d" % num)
if __name__ == '__main__':
print("线程创建前:num = %d" % num)
t1 = threading.Thread(target=addNum)
t1.start()
t2 = threading.Thread(target=showNum)
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 10-线程-解决全局变量共享问题.py
线程创建前:num = 0
线程1--num = 100000
线程2--num = 200000
虽然能解决,但是效率不高
解决方法2 -- 互斥锁
Lock
类
当多个线程几乎同时修改某一个共享数据时,需要进行同步控制,最简单的同步机制就是互斥锁(
lock
)。
from threading import Thread, Lock
import time
num = 0
def addNum():
global num
lk.acquire() # 上锁
for i in range(100000):
num += 1
lk.release() # 解锁
print("线程1--num = %d" % num)
def showNum():
global num
lk.acquire() # 上锁
for i in range(100000):
num += 1
lk.release() # 解锁
print("线程2--num = %d" % num)
if __name__ == '__main__':
print("线程创建前:num = %d" % num)
lk = Lock() # 创建锁
t1 = Thread(target=addNum)
t1.start()
t2 = Thread(target=showNum)
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 11-线程-解决全局变量共享问题-互斥锁.py
线程创建前:num = 0
线程1--num = 100000
线程2--num = 200000
Lock
默认是没有上锁的
无论哪一个线程先上锁,另一个便无法上锁,进行阻塞,等待解锁
Ps:在不修改只读取全局变量的时候,是不需要加锁的
非共享数据(局部)
import threading
import time
class NewClass(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
num = 10
print("当前是 %s 在执行" % self.name)
if self.name == "Thread-1":
num += 1
else:
time.sleep(2)
print("线程:%s ,num = %d" % (self.name, num))
if __name__ == '__main__':
lk = threading.Lock()
t1 = NewClass()
t1.start()
t2 = NewClass()
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 12-线程-非共享数据.py
当前是 Thread-1 在执行
线程:Thread-1 ,num = 11
当前是 Thread-2 在执行
线程:Thread-2 ,num = 10
Thread-2
的值并没有被改变,说明 不同线程执行同一函数时,其内的所有数据是独有的
不同线程间的全局变量是共享的,局部变量是私有的
各线程间的非共享数据是独立的,也就不需要加锁了
死锁
在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
import threading
import time
class NewClass(threading.Thread):
def run(self):
if lkA.acquire(): # 默认为开锁,正常执行后上锁
print(self.name + "---do1--up---")
time.sleep(1)
if lkB.acquire(): # 等待 lkB 解锁
print(self.name + "---do2--down---")
lkB.release()
lkA.release()
class NewClassTwo(threading.Thread):
def run(self):
if lkB.acquire(): # 默认为开锁,正常执行后上锁
print(self.name + "---do2--up---")
time.sleep(1)
if lkA.acquire(): # 等待 lkA 解锁
print(self.name + "---do2--down---")
lkA.release()
lkB.release()
if __name__ == '__main__':
lkA = threading.Lock()
lkB = threading.Lock()
t1 = NewClass()
t2 = NewClassTwo()
t1.start()
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 13-死锁.py
Thread-1---do1--up---
Thread-2---do1--up---
| # 持续等待
lkA
在等待lkB
解锁,lkB
又在等待lkA
解锁,相互僵持,形成死锁
避免死锁
- 程序设计尽量避开(银行家算法)
- 上锁时添加超时时间
acquire(blocking=True, timeout=-1)
同步
同步就是协调步调,按预定的先后次序进行运行。如:你先说,我再说
这个同不是一起执行,而是协同、协助、相互配合。如进程、线程同步,可理解为进程或线程A与B一块配合,A执行的一定程度时依靠B的某个结果,于是停下来,示意B运行,再将结果给A,A继续运行
import threading
import time
class NewClassT1(threading.Thread):
def run(self):
while True:
if lkA.acquire():
print("---AAA---")
time.sleep(1)
lkB.release()
class NewClassT2(threading.Thread):
def run(self):
while True:
if lkB.acquire():
print("---BBB---")
time.sleep(1)
lkC.release()
class NewClassT3(threading.Thread):
def run(self):
while True:
if lkC.acquire():
print("---CCC---")
time.sleep(1)
lkA.release()
if __name__ == '__main__':
lkA = threading.Lock()
lkB = threading.Lock()
lkB.acquire()
lkC = threading.Lock()
lkC.acquire()
t1 = NewClassT1()
t2 = NewClassT2()
t3 = NewClassT3()
t1.start()
t2.start()
t3.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 14-同步的应用.py
---AAA---
---BBB---
---CCC---
---AAA---
---BBB---
---CCC---
---AAA---
---BBB---
---CCC---
---AAA---
---BBB---
...
生产者与消费者模式(数据生产与数据处理)
在做爬虫的时候,会遇到爬去的数据跟不上处理速度或处理速度跟不上爬取速度,会堆积在那里,所造成一些问题,就需要在爬取与处理间放一个缓存来解决,可以用
Queue
来解决
Queue
类
python2
导入方式:from Queue import Queue
python3
导入方式:from queue import Queue
import threading
import time
from queue import Queue
class NewClassT1(threading.Thread):
def run(self):
global queue
count = 0
while True:
if queue.qsize() < 1000:
for i in range(100):
count = count + 1
msg = "生成了:" + str(count)
queue.put(msg)
print(msg)
time.sleep(0.5)
class NewClassT2(threading.Thread):
def run(self):
global queue
while True:
if queue.qsize() > 100:
for i in range(3):
msg = self.name + "处理了:" + queue.get()
print(msg)
time.sleep(1)
if __name__ == '__main__':
queue = Queue()
for i in range(500):
queue.put("初始添加数据:" + str(i))
for i in range(2):
t1 = NewClassT1()
t1.start()
for i in range(5):
t2 = NewClassT2()
t2.start()
root@H2o2:~/文档/PycharmProjects/进程与线程# python3 15-生产者与消费者模式.py
生成了:1
生成了:2
生成了:3
生成了:4
生成了:5
生成了:6
生成了:1
Thread-3处理了:初始添加数据:0
Thread-3处理了:初始添加数据:3
Thread-5处理了:初始添加数据:2
Thread-4处理了:初始添加数据:1
生成了:2
生成了:7
Thread-6处理了:初始添加数据:6
Thread-4处理了:初始添加数据:7
生成了:8
生成了:9
Thread-6处理了:初始添加数据:8
生成了:3
生成了:4
Thread-3处理了:初始添加数据:4
Thread-5处理了:初始添加数据:5
Thread-5处理了:初始添加数据:12
Thread-6处理了:初始添加数据:11
生成了:10
Thread-4处理了:初始添加数据:9
Thread-7处理了:初始添加数据:10
生成了:11
Thread-7处理了:初始添加数据:13
生成了:12
Thread-7处理了:初始添加数据:14
生成了:13
生成了:14
生成了:5
生成了:15
生成了:6
生成了:16
...
ThreadLocal
多个线程访问同一个函数,函数内(局部)的数据是独立互不影响的
import threading
import time
def run():
num = 100
num += 1
print(num)
if __name__ =='__main__':
t1 = threading.Thread(target=run)
t2 = threading.Thread(target=run)
t1.start()
time.sleep(2)
t2.start()
h2o2@h2o2-PC:~/Documents/01-工程文件/PycharmProjects/04-进程与线程$ python3 16-线程-ThreadLocal对象.py
101
101
A函数要得到B函数的值,要么返回值,要么全局变量,在线程中,两种方式要么用不了,要么太枯燥麻烦。这时候可以用全局字典
import threading
import time
global_dict = {}
def run_th(num):
global_dict[threading.current_thread()] = num
cc = global_dict[threading.current_thread()]
print("线程:%s -- 值:%s" % (threading.current_thread().name, cc))
do_run_1()
do_run_2()
def do_run_1():
th = threading.current_thread().name
vul_1 = global_dict[threading.current_thread()]
print("当前线程是:%s -- 值:%s" % (th, vul_1))
def do_run_2():
th = threading.current_thread().name
vul_1 = global_dict[threading.current_thread()]
print("当前线程是:%s -- 值:%s" % (th, vul_1))
if __name__ == '__main__':
t1 = threading.Thread(target=run_th, args=(1,))
t2 = threading.Thread(target=run_th, args=(2,))
t1.start()
# time.sleep(2)
t2.start()
h2o2@h2o2-PC:~/Documents/01-工程文件/PycharmProjects/04-进程与线程$ python3 17-线程-全局字典传值.py
线程:Thread-1 -- 值:1
当前线程是:Thread-1 -- 值:1
当前线程是:Thread-1 -- 值:1
线程:Thread-2 -- 值:2
当前线程是:Thread-2 -- 值:2
当前线程是:Thread-2 -- 值:2
全局字典也麻烦,不太合适..最好、最简单的方法还是
ThreadLocal
Thread-local
数据是其值是线程特定的数据。
无论有多少线程,
threadlocal
的值都是各自线程的值,不会因为下一个线程将threadlocal
值修改后而改变上一个线程的threadlocal
值
import threading
def do_run_1():
st = local_1.number
print("线程:%s -- 值:%s" % (threading.current_thread().name, st))
def run_th(num):
local_1.number = num # 绑定local_1 的属性number 的值 num
do_run_1()
if __name__ == '__main__':
# 创建全局threadlocal 对象
local_1 = threading.local()
t1 = threading.Thread(target=run_th, args=(1,), name="T-A")
t2 = threading.Thread(target=run_th, args=(2,), name="T-B")
t1.start()
t2.start()
t1.join()
t2.join()
h2o2@h2o2-PC:~/Documents/01-工程文件/PycharmProjects/04-进程与线程$ python3 18-线程-ThreadLocal.py
线程:T-A -- 值:1
线程:T-B -- 值:2
异步
import time
from multiprocessing import Pool
import os
def do_run_1():
print("---线程池中的进程的 pid:%d,ppid:%d--" % (os.getpid(), os.getppid()))
for i in range(3):
print("---%d---" % i)
time.sleep(1)
return "FFF"
def do_run_2(args):
print("回调函数--pid=%d" % os.getpid())
print("回调函数返回值=%s" % args)
if __name__ == '__main__':
pool = Pool(3)
pool.apply_async(func=do_run_1, callback=do_run_2) # 进程池中加入一个进程
time.sleep(5) # 子进程结束后,回调函数由 父进程操作
print("---主线程--pid=%d ---" % os.getpid()) # 主进程在子进程结束后,暂停自己的事情去执行回调函数,后执行自己的任务
h2o2@h2o2-PC:~/Documents/01-工程文件/PycharmProjects/04-进程与线程$ python3 19-异步.py
---线程池中的进程的 pid:7987,ppid:7986--
---0---
---1---
---2---
回调函数--pid=7986
回调函数返回值=FFF
---主线程--pid=7986 ---
GIL(全局解释器锁)
- 同步:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,说明不支持并发也不支持并行
- 异步:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后电话以后继续吃饭,说明支持并发
- 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行
并发:交替处理多个任务的能力;执行的任务大于核数
并行:同时处理多个任务的能力;执行的任务小于核数
并发的关键是你有处理多个任务的能力,不一定要同时
并行的关键是你有同时处理多个任务的能力,强调的是同时.
所以它们最大的区别就是:是否是『同时』处理任务。
对于一个多核cpu来说并行要比并发快的多
cpython解释器中存在一个GIL(全局解释器锁),它的作用就是保证同一时刻只有一个线程可以执行代码,因此造成了我们使用多线程的时候无法实现并行。
多核CPU最好的多任务选择还是多进程,多进程的效率远远大于多进程
Python的GIL是什么鬼,多线程性能究竟如何
解决方式
- 使用多进程
- 若是必须用 多线程,关键地方可以用 C 语言解决