第三十章 网路编程------线程
1.什么是线程
线程是操作系统最小的运算调度单位,被包含在进程中,一个线程就是一个固定的执行流程
线程和进程的关系 :
线程不能单独存在,必须存在与进程中
进程是一个资源单位,其包含了运行程序所需的所有资源
线程才是真正的执行单位
没有线程,进程中的资源无法被利用起来,所以一个进程至少包含一个线程,称之为主线程
当开启一个程序时,操作系统就会为自己创建一个主线程
线程可以有程序后期开启,自己开启线程称之为子线程
为什么需要线程:
目的:提高效率
就像以一个车间,如果产量跟不上,就再创建一条流水线
也可以再造一个新车间,那需要把原料运过去,这个过程是非常耗时的
所以通常情况是创建新的流水线,而不是车间 即 线程
如何使用:
使用方法与进程一样
开启线程的代码可以放在任何位置,开启进程必须放在判断下面
from threading import Thread,current_thread
import time
def task():
print("2",current_thread())
print("子线程running")
time.sleep(10)
print("子线程over")
# 使用方法一 直接实例化Thread类
if __name__ == '__main__':
t = Thread(target=task)
t.start()
# task()
# 执行顺序不固定 如果开启线程速度足够快 可能子线程先执行
print("主线程over")
print("1",current_thread())
# 使用方法二 继承Thread 覆盖run方法
class MyThread(Thread):
def run(self):
print("子线程run!")
m = MyThread()
print("主线over")
# 使用方法和多进程一模一样 开启线程的代码可以放在任何位置 开启进程必须放在判断下面
线程的特点:
创建开销小
同一个进程中的多个线程数据是共享的
多个线程之间,是平等的没有父子关系 所有线程的PID都是相同的
# 创建线程开销对比
import os
from threading import Thread
from multiprocessing import Process
import time
def task():
# print("hello")
print(os.getpid())
pass
if __name__ == '__main__':
st_time = time.time()
ts = []
for i in range(100):
t = Thread(target=task)
# t = Process(target=task)
t.start()
ts.append(t)
for t in ts:
t.join()
print(time.time()-st_time)
print("主over")
守护线程
一个线程可以设置为另一个线程的守护线程
特点:被守护线程结束后守护线程也随之结束
守护线程会等到所有非守护线程结束后结束,前提是除了主线程之外,还有别的非守护线程
当然如果守护线程已经完成任务,立马就结束了
from threading import Thread
import time
def task():
print("子1running......")
time.sleep(100)
print("子1over......")
def task2():
print("子2running......")
time.sleep(4)
print("子2over......")
t = Thread(target=task)
t.daemon = True
t.start()
t2 =Thread(target=task2)
t2.start()
print("主over")
线程互斥锁
共享意味着竞争
线程中也存在安全问题
多线程可以并发执行,一旦并发了并且访问了同一个资源就会有问题
解决方案:互斥锁
from threading import Thread,enumerate,Lock
import time
number = 10
lock = Lock()
def task():
global number
lock.acquire()
a = number
time.sleep(0.1)
number = a - 1
lock.release()
for i in range(10):
t = Thread(target=task)
t.start()
for t in enumerate()[1:]:
# print(t)
t.join()
print(number)
from threading import Lock, current_thread, Thread
"""
死锁问题
当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁
这时候程序就会进程无限卡死状态 ,这就称之为死锁
例如:
要吃饭 必须具备盘子和筷子 但是一个人拿着盘子 等筷子 另一个人拿着筷子等盘子
如何避免死锁问题
锁不要有多个,一个足够
如果真的发生了死锁问题,必须迫使一方先交出锁
"""
import time
# 盘子
lock1 = Lock()
# 筷子
lock2 = Lock()
def eat1():
lock1.acquire()
print("%s抢到了盘子" % current_thread().name)
time.sleep(0.5)
lock2.acquire()
print("%s抢到了筷子" % current_thread().name)
print("%s开吃了!" % current_thread().name)
lock2.release()
print("%s放下筷子" % current_thread().name)
lock1.release()
print("%s放下盘子" % current_thread().name)
def eat2():
lock2.acquire()
print("%s抢到了筷子" % current_thread().name)
lock1.acquire()
print("%s抢到了盘子" % current_thread().name)
print("%s开吃了!" % current_thread().name)
lock1.release()
print("%s放下盘子" % current_thread().name)
lock2.release()
print("%s放下筷子" % current_thread().name)
t1 = Thread(target=eat1)
t2 = Thread(target=eat2)
t1.start()
t2.start()
可重入锁 了解
Rlock 称之为递归锁或者可重入锁
Rlock不是用来解决死锁问题的
与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次 如果一个线程已经执行过acquire 其他线程将无法执行acquire
from threading import RLock, Lock, Thread
# l = Lock()
#
# l.acquire()
# print("1")
# l.acquire()
# print("2")
l = RLock()
# l.acquire()
# print("1")
# l.acquire()
# print("2")
def task():
l.acquire()
print("子run......")
l.release()
# 主线程锁了一次
l.acquire()
l.acquire()
l.release()
l.release()
t1 = Thread(target=task)
t1.start()
信号量 了解
"""
信号量 了解
Lock RLock
可以现在被锁定的代码 同时可以被多少线程并发访问
Lock 锁住一个马桶 同时只能有一个
Semaphore 锁住一个公共厕所 同时可以来一堆人
用途: 仅用于控制并发访问 并不能防止并发修改造成的问题
"""
from threading import Semaphore, Thread
import time
s = Semaphore(5)
def task():
s.acquire()
print("子run")
time.sleep(3)
print("子over")
s.release()
for i in range(10):
t = Thread(target=task)
t.start()