了解线程
线程,也叫轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
(百度百科之定义)
Python中的多线程
Python中的多线程是假的多线程!因为Python代码的执行由Python解释器来控制,而对Python解释器的访问由全局解释器锁(GIL)来控制,这个锁保证了同一时间只有一个线程在执行。
在多线程的环境中,Python解释器以时间切片的方法,按以下方式运行:
1.设置GIL锁 (就绪状态)
2.切换线程,执行(运行状态)
3.运行结束,把线程休眠(阻塞状态)
4.解锁,并重复以上步骤
所以,多线程在Python中是交替执行的,单位时间内,不管是几核的系统,只能跑一个线程,然后时间片切换
创建一个线程
那么多线程怎么使用多核:
1、重写python编译器(官方cpython)如使用:PyPy解释器
2、调用C语言的链接库
import _thread
import threading
from threading import Thread
import time
def doSth(arg):
# 拿到当前线程的名称和线程号id
threadName = threading.current_thread().getName()
tid = threading.current_thread().ident
for i in range(5):
print("%s *%d @%s,tid=%d" % (arg, i, threadName, tid))
time.sleep(2)
# 使用_thread.start_new_thread开辟子线程
def simpleThread():
# 创建子线程,执行doSth
# 用这种方式创建的线程为【守护线程】(主线程死去“护卫”也随“主公”而去)
_thread.start_new_thread(doSth, ("拍森",))
mainThreadName = threading.current_thread().getName()
print(threading.current_thread())
# 5秒的时间以内,能看到主线程和子线程在并发打印
for i in range(5):
print("劳资是主线程@%s" % (mainThreadName))
time.sleep(1)
# 阻塞主线程,以使【守护线程】能够执行完毕
while True:
pass
# 通过创建threading.Thread对象实现子线程
def threadingThread():
# 默认不是【守护线程】
t = threading.Thread(target=doSth, args=("大王派我来巡山",))
# t.setDaemon(True) # 设置为守护线程
t.start() # 启动线程,调用run()方法
# 5秒的时间以内,能看到主线程和子线程在并发打印
for i in range(5):
print("劳资是大王@%s" % (threading.current_thread().getName()))
time.sleep(1)
# 继承于threading.Thread
class MyThread(threading.Thread):
# 构造方法一定要执行super方法,否则不能构成事实上的线程类
# 继承法的优势在于可以灵活自定义(构造方法传参,在类内部封装很多方法)
# 注意:构造方法是跑在【主调线程】(创建当前实例的线程)
def __init__(self, name, task, subtask):
super().__init__()
self.name = name # 覆盖了父类的name
self.task = task # MyThread自己的属性
self.subtask = subtask
# 覆写父类的run方法,
# run方法以内为【要跑在子线程内的业务逻辑】(thread.start()会触发的业务逻辑)
def run(self):
for i in range(5):
print("【%s】并【%s】 *%d @%s" % (self.task, self.subtask, i, threading.current_thread().getName()))
time.sleep(2)
# 通过继承threading.Thread类,进而创建对象实现子线程
def classThread():
mt = MyThread("小分队I", "巡山", "扫黄")
mt.start()
for i in range(5):
tid = threading.current_thread().ident
print("劳资是大王@%s,tid=%d" % (threading.current_thread().getName(), tid))
time.sleep(1)
print("主线程over")
# 几个重要的API
def importantAPI():
print(threading.currentThread()) # 返回当前的线程变量
# 创建五条子线程
t1 = threading.Thread(target=doSth, args=("巡山",))
t2 = threading.Thread(target=doSth, args=("巡水",))
t3 = threading.Thread(target=doSth, args=("巡鸟",))
t1.start() # 开启线程
t2.start()
t3.start()
print(t1.isAlive()) # 返回线程是否活动的
print(t2.isDaemon()) # 是否是守护线程
print(t3.getName()) # 返回线程名
t3.setName("巡鸟") # 设置线程名
print(t3.getName())
print(t3.ident) # 返回线程号
# 返回一个包含正在运行的线程的list
tlist = threading.enumerate()
print("当前活动线程:", tlist)
# 返回正在运行的线程数量(在数值上等于len(tlist))
count = threading.active_count()
print("当前活动线程有%d条" % (count))
if __name__ == '__main__':
simpleThread()
# threadingThread()
# classThread()
# importantAPI()
pass
创建一个多线程
'''
少林功夫+唱歌+跳舞
'''
import threading
import random
# 功夫、铁头功+金刚腿
import time
def kongfu(kind):
print("我是%s" % kind)
# 唱歌
def sing(who, what):
# time.sleep(3)
print("%s:%s" % (who, what))
# 跳舞
def dance():
time.sleep(1)
print("蹦瞎卡拉卡,蹦瞎卡拉卡")
if __name__ == '__main__':
singList = ["少林功夫好耶", "真滴好啊", "少林功夫够劲", "系好劲", "我系铁头功", "无敌铁头功",
"我系金刚腿", "金刚腿", "他系铁头功", "哦哇,哦哇........"]
t1 = threading.Thread(target=kongfu, args=("铁头功",))
t2 = threading.Thread(target=kongfu, args=("金刚腿",))
for i in range(len(singList)):
t3 = threading.Thread(target=sing, args=("周星星", random.choice(singList)))
t4 = threading.Thread(target=sing, args=("大师兄", random.choice(singList)))
t3.start()
t3.join()
t4.start()
t4.join()
t5 = threading.Thread(target=dance)
# 线程同步(依次执行)
t1.start()
t2.start()
t5.start()
# join()等待线程执行完毕,
# 线程异步(并发执行)
t1.join()
t2.join()
t5.join()
pass
线程冲突
'''
【线程冲突】示例:
多个线程并发访问同一个变量而互相干扰
互斥锁
状态:锁定/非锁定
#创建锁
lock = threading.Lock()
#锁定
lock.acquire()
#释放
lock.release()
'''
import threading
import time
money = 0
# CPU分配的时间片不足以完成一百万次加法运算,
# 因此结果还没有被保存到内存中就被其它线程所打断
def addMoney():
global money
for i in range(1000000):
money += 1
print(money)
# 创建线程锁
lock = threading.Lock()
def addMoneyWithLock():
print("addMoneyWithLock")
time.sleep(1)
global money
# print(lock.acquire())
# if lock.acquire():
# for i in range(1000000):
# money += 1
# lock.release()
# 独占线程锁
with lock: # 阻塞直到拿到线程锁
# -----下面的代码只有拿到lock对象才能执行-----
for i in range(1000000):
money += 1
# 释放线程锁,以使其它线程能够拿到并执行逻辑
# ----------------锁已被释放-----------------
print(money)
# 5条线程同时访问money变量,导致结果不正确
def conflictDemo():
for i in range(5):
t = threading.Thread(target=addMoney)
t.start()
# 通过线程同步(依次执行)解决线程冲突
def handleConflictBySync():
for i in range(5):
t = threading.Thread(target=addMoney)
t.start()
t.join() # 一直阻塞到t运行完毕
# 通过依次独占线程锁解决线程冲突
def handleConflictByLock():
# 并发5条线程
for i in range(5):
t = threading.Thread(target=addMoneyWithLock)
t.start()
if __name__ == '__main__':
# conflictDemo()
# handleConflictBySync()
handleConflictByLock()
# addMoneyWithLock()
pass
死锁
'''
互相锁住对方线程需要的资源,造成死锁局面
'''
import threading
from threading import Thread
import time
# 自尊锁
boyHonor = threading.Lock()
girlHonor = threading.Lock()
honor = threading.RLock()
class Boy(Thread):
def run(self):
print("妈蛋气死劳资了战斗开始...")
# 锁住Girl所需的boyHonor对象
if boyHonor.acquire():
print("Boy:Gril必须先道歉!")
time.sleep(1)
# 需要girlHonor才能继续执行
if girlHonor.acquire(timeout=-1):
girlHonor.release()
boyHonor.release()
print("Boy:im sorry too!")
else:
print("Boy:....")
print("Boy战斗结束")
class Girl(Thread):
def run(self):
print("妈蛋气死老娘了战斗开始...")
# 锁住Boy所需的girlHonor对象
if girlHonor.acquire():
print("Gril:Boy必须先道歉!")
# 需要boyHonor才能继续执行
if boyHonor.acquire(timeout=-1):
boyHonor.release()
girlHonor.release()
print("Girl:im sorry too!")
else:
print("Girl:妈蛋分手!")
print("Girl战斗结束")
if __name__ == '__main__':
Boy().start()
Girl().start()
pass
递归锁
'''
互相锁住对方线程需要的资源,造成死锁局面
递归锁,用于解决死锁的问题,可重复锁
'''
import threading
from threading import Thread
import time
# 自尊锁
honor = threading.RLock()
class Boy(Thread):
def run(self):
print("妈蛋气死劳资了战斗开始...")
# 锁住Girl所需的honor对象
if honor.acquire():
print("Boy:Gril必须先道歉!")
time.sleep(1)
# 需要honor才能继续执行
if honor.acquire(timeout=-1):
honor.release()
honor.release()
print("Boy:im sorry too!")
else:
print("Boy:....")
print("Boy战斗结束")
class Girl(Thread):
def run(self):
print("妈蛋气死老娘了战斗开始...")
# 锁住Boy所需的honor对象
if honor.acquire():
time.sleep(2)
print("Gril:Boy必须先道歉!")
# 需要honor才能继续执行
if honor.acquire(timeout=-1):
honor.release()
honor.release()
print("Girl:im sorry too!")
else:
print("Girl:妈蛋分手!")
print("Girl战斗结束")
if __name__ == '__main__':
Boy().start()
Girl().start()
pass
event实现线程通信
'''
Event实现线程通信
'''
import threading
import time
def handleEvent(event):
global data
print("子线程正在循环监听事件...")
tname = threading.current_thread().getName()
while True:
event.wait() # 阻塞监听事件
time.sleep(0.1)
# 3.拿到事件数据并处理
print("%s处理事件*%d" % (tname, data), event)
event.clear()
print("事件已被处理")
pass
# 在栈内存中声明data
data = 0
if __name__ == '__main__':
event = threading.Event()
# 创建事件监听线程
threading.Thread(target=handleEvent, args=(event,), name="foo").start()
threading.Thread(target=handleEvent, args=(event,), name="bas").start()
threading.Thread(target=handleEvent, args=(event,), name="pig").start()
for i in range(10):
# 1.改变数据
# data = 1 #重新声明局部变量data
data += 1
# 2.发布事件,通知数据已改变
event.set()
print("\n事件已发布*%d\n" % (data))
time.sleep(1)
pass
condition实现线程通信
'''
通过threading.Condition实现线程通信:
生产消费模型
'''
import random
import threading
import time
# 线程间交替通信的信物
condition = threading.Condition()
# 产品容器
plist = []
# 产品
class Product:
def __init__(self, name):
self.name = name
def __str__(self):
return "Product:%s" % (self.name)
# 生产者线程
class Producer(threading.Thread):
def run(self):
while True:
# 阻塞等待获得condition对象
with condition:
# 此处已经获取到condition对象(condition.acquire())
print("生产者拿到condition")
p = Product(random.randint(100, 999))
plist.append(p)
print("生产者生产了", p)
time.sleep(1)
condition.notify() # 通知消费者,谁wait()了就通知谁
condition.wait() # 监听消费者通知,谁wait代表谁希望被notify(wait中会释放condition)
# with走完,交出condition
# 此处condition已释放(condition.release())
# 消费者线程
class Consumer(threading.Thread):
def run(self):
while True:
# 阻塞等待获得condition对象(本例中一直等到生产者with condition内的代码走完)
with condition:
# 此处已经获取到condition对象(condition.acquire())
print("消费者拿到conditon")
try:
p = plist.pop()
print("%s消费者消费了" % (self.name), p)
condition.notify() # 通知生产者生产,谁with了相同condition且wait就通知谁
condition.wait() # 等候生产者消息(wait中会释放condition)
print("fuck")
except IndexError:
print("消费者:冰箱里空空如也")
# 此处condition已释放(condition.release())
if __name__ == '__main__':
Producer().start()
Consumer().start()
pass
condition实现线程通信2
'''
通过threading.Condition实现线程通信:
生产消费模型
'''
import random
import threading
import time
# 线程间交替通信的信物
condition = threading.Condition()
# 产品容器
plist = []
# 产品
class Product:
def __init__(self, name):
self.name = name
def __str__(self):
return "Product:%s" % (self.name)
# 生产者线程
class Producer(threading.Thread):
def run(self):
while True:
# 阻塞等待获得condition对象
with condition:
# 此处已经获取到condition对象(condition.acquire())
print("生产者拿到condition")
p = Product(random.randint(100, 999))
p2 = Product(random.randint(100, 999))
plist.append(p)
plist.append(p2)
print("生产者生产了", p)
print("生产者生产了", p2, "\n")
time.sleep(1)
# condition.notify() # 通知消费者,谁wait()了就通知谁
condition.notify_all()
condition.wait() # 监听消费者通知,谁wait代表谁希望被notify(wait中会释放condition)
# with走完,交出condition
# 此处condition已释放(condition.release())
# 消费者线程
class Consumer(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
while True:
# 阻塞等待获得condition对象(本例中一直等到生产者with condition内的代码走完)
with condition:
# 此处已经获取到condition对象(condition.acquire())
print("消费者%s拿到conditon" % (self.name))
try:
p = plist.pop()
print("%s消费者消费了" % (self.name), p)
print("fuck")
except IndexError:
print("消费者:冰箱里空空如也")
condition.notify() # 通知生产者生产,谁with了相同condition且wait就通知谁
condition.wait() # 等候生产者消息(wait中会释放condition)
# 此处condition已释放(condition.release())
if __name__ == '__main__':
Producer().start()
Consumer("foo").start()
Consumer("bas").start()
pass
semaphore调度线程
'''
使用Semaphore调度线程:控制最大并发量
'''
import threading
import time
# 允许最大并发量3
sem = threading.Semaphore(3)
def doSth(arg):
with sem:
tname = threading.current_thread().getName()
print("%s正在执行【%s】" % (tname, arg))
time.sleep(1)
print("-----%s执行完毕!-----\n" % (tname))
time.sleep(0.1)
if __name__ == '__main__':
# 开启10条线程
for i in range(10):
threading.Thread(target=doSth, args=("巡山",), name="小分队%d" % (i)).start()
pass
barrier调度线程
'''
使用Barrier调度线程:足三而行
'''
import threading
import time
def sayGoodbye():
print("我们先撤尔等殿后")
# 3=足三而行
# action=放行时回调
# timeout=超时时间(等待超时会抛出BrokenBarrierError异常)
bar = threading.Barrier(3, action=sayGoodbye, timeout=5)
def doSth(arg):
# 阻塞等待足三而行
# 等待超时会抛出BrokenBarrierError异常
tname = threading.current_thread().getName()
try:
bar.wait()
except threading.BrokenBarrierError:
print("栅栏已被捣毁,呼叫总部支援!")
print("%s:妈的智障,劳资还要回家学拍森呢!" % (tname))
pass
print("%s正在执行【%s】" % (tname, arg))
time.sleep(1)
print("-----%s执行完毕!-----\n" % (tname))
time.sleep(0.1)
if __name__ == '__main__':
# 开启10条线程
for i in range(10):
threading.Thread(target=doSth, args=("巡山",), name="小分队%d" % (i)).start()
pass
timer延时线程
'''
Timer延时线程
'''
import threading
import time
def doSth(arg):
tname = threading.current_thread().getName()
print("%s正在执行【%s】" % (tname, arg))
time.sleep(1)
print("-----%s执行完毕!-----\n" % (tname))
if __name__ == '__main__':
# 延时5秒执行doSth("巡山")
threading.Timer(5, doSth, args=["巡山"]).start()
pass
线程池
'''
使用线程池
'''
import random
import threading
import threadpool
import time
# =================================================================
# 参数皆为单个元素,用于执行只有一个参数的并发函数请求
argsList1 = [
"张三丰", "赵四", "王五", "六爷", "洪七公", "朱重八", "马英九"
]
# 该函数只有一个参数,参数列表使用argsList1格式
def kill(who):
tname = threading.current_thread().getName()
# print(threading.current_thread().isDaemon())
print("%s开始杀%s..." % (tname, who))
time.sleep(random.randint(1, 5))
print("-----%s杀死了%s-----" % (tname, who))
pass
# =================================================================
# =================================================================
# 当请求函数包含1个以上的参数时,必须使用如下格式:
# [ ([位置参数列表],{关键字参数字典}),... ]
argsList2 = [
([1, "今天"], {"who": "张三丰", "reason": "已经很老了"}),
([2, "明天"], {"who": "赵四", "reason": "我是刘能的朋友"}),
([3, "后天"], {"who": "王五", "reason": "隔壁老王嫌疑人"}),
([4, "今天"], {"who": "六爷"}),
([5, "明天"], {"reason": "我是刘能的朋友"}),
([6, "后天"], None),
]
# 该函数有位置参数和关键字参数,参数列表使用argsList2格式
def killGood(order, when, who="No one", reason="凡人皆有一死"):
tname = threading.current_thread().getName()
# print(threading.current_thread().isDaemon())
print("%s开始杀%s...,排名%d,时间%s,原因:%s" % (tname, who, order, when, reason))
time.sleep(random.randint(1, 5))
print("-----%s杀死了%s-----" % (tname, who))
# print(5 / 0)
# print("fuck")
return "well done"
pass
# =================================================================
# 请求执行结束回调
# request=已完成的请求
# result=任务的返回值
def onKillReturn(request, result):
print("onKillReturn", request, result)
# 异常处理回调
# request=发声异常的请求
# exception=请求内发声的异常
def handleMyException(request, exception):
print("handleMyException", request, exception)
if __name__ == '__main__':
# 创建一个最大并发为4的线程池
pool = threadpool.ThreadPool(4)
# 创建一组执行kill()函数的请求,参数各异
# requests = threadpool.makeRequests(kill, argsList1)
# requests = threadpool.makeRequests(killGood, argsList2)
# killGood=要并发执行的任务
# argsList2=killGood的参数列表
# callback=任务结束的回调函数
# exc_callback=异常处理回调函数
requests = threadpool.makeRequests(killGood, argsList2, callback=onKillReturn, exc_callback=handleMyException)
# 将这组请求丢入线程池
for req in requests:
pool.putRequest(req)
# 阻塞等待全部请求返回(线程池创建的并发默认为【守护线程】)
pool.wait()
print("over")
pass
threadlocal
import threading
from threading import Thread
import time
'''
这个东西可以用在那些地方呢,比如下载,现在都是多线程下载了,
就像酷狗那样,可以同时下载很多首歌曲,那么就可以利用这个方法
来保存每个下载线程的数据,比如下载进度,下载速度之类的
'''
# threading.local()这个方法的特点用来保存一个全局变量,
# 但是这个全局变量只有在当前线程才能访问,
data = threading.local()
print(type(data))
def doSomething(num):
tName = threading.current_thread().getName()
'''
localVal.val = name这条语句可以储存一个变量到当前线程,
如果在另外一个线程里面再次对localVal.val进行赋值,
那么会在另外一个线程单独创建内存空间来存储,
也就是说在不同的线程里面赋值不会覆盖之前的值,
因为每个线程里面都有一个单独的空间来保存这个数据,
而且这个数据是隔离的,其他线程无法访问
'''
data.num = num
for i in range(5):
data.num += 1
time.sleep(1)
print(tName, "data.num=", data.num)
pass
def doSomethingII(num):
tName = threading.current_thread().getName()
data.num = num
for i in range(5):
data.num += "1"
time.sleep(1)
print(tName, "data.num=", data.num)
pass
if __name__ == '__main__':
# threading.local的例子
t1 = Thread(target=doSomething, args=(1,), name="foo")
t2 = Thread(target=doSomethingII, args=("1",), name="bas")
t1.start()
t2.start()
print("over")