线程
目录
线程
线程基础
什么是线程?
进程线程其实都是虚拟单位,都是用来帮助我们形象的描述某种事物。
进程:是一个资源单位
线程:执行单位
我们可以将内存比如工厂,那么进程就相当于是工厂里面的车间,而线程就相当于是车间里面的流水线。
ps:每个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中,提供代码运行所需要的资源。
为什么要有线程?
因为开线程需要申请内存空间,很耗资源,也需要"拷贝代码",也很耗资源。
而线程,一个进程内可以起多个线程,并且线程与线程之间数据是可以共享的。
ps:开启线程的开销要远远小于开启进程的开销
创建线程的两种方式
第一种:通过Thread模块直接创建
from threading import Thread
import time
def task(name):
print(f"{name} is running")
time.sleep(3)
print(f'{name} is over')
# 开线程不需要在__main__代码块内,但是习惯性的还是写在__main__代码块内
t = Thread(target=task,args=("egon",))
t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 小的代码执行完 线程就已经开启了
print("主")
egon is running主
egon is over
第二种:通过自定义类来实现
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
super(MyThread, self).__init__()
self.name = name
def run(self):
print(f"{self.name} is running")
time.sleep(3)
print(f"{self.name} is over")
t = MyThread('egon')
t.start()
print("主")
egon is running主
egon is over
线程对象及其他方法
pid 获取进程号
from threading import Thread
import time
import os
def task(name,i):
print(f"{name} is running")
print("子",os.getpid())
time.sleep(i)
print(f'{name} is over')
t = Thread(target=task,args=("egon",1))
t1 = Thread(target=task,args=("jason",2))
t.start()
t1.start()
print("主")
print("主",os.getpid())
egon is running
子 3348
jason is running主
主 3348
子 3348
egon is over
jason is over
current_thread().name 当前线程变量的名字
from threading import Thread,current_thread
import time
import os
def task(name,i):
print(f"{name} is running")
print("子current_thread:",current_thread().name)
time.sleep(i)
print(f'{name} is over')
t = Thread(target=task,args=("egon",1))
t1 = Thread(target=task,args=("jason",2))
t.start()
t1.start()
print("主")
print("主current_thread:",current_thread().name)
egon is running
子current_thread: Thread-1
jason is running主
主current_thread: MainThread
子current_thread: Thread-2
egon is over
jason is over
active_count() 正在运行的线程数量
from threading import Thread,active_count
import time
import os
def task(name,i):
print(f"{name} is running")
time.sleep(i)
print(f'{name} is over') #
t = Thread(target=task,args=("egon",1))
t1 = Thread(target=task,args=("jason",2))
t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
t1.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
t1.join() # 主线程等待子线程运行我完毕后执行
print("当前正在活跃的线程数",active_count())
print("主")
egon is running
jason is running
egon is over
jason is over
当前正在活跃的线程数 1
主
内存共享问题
多个线程共享一个进程里面的数据。如果其中一个线程对进程的里面的数据进行了改变,则这个数据就发生了变化。
from threading import Thread
money = 666
def task():
global money
money = 999
t = Thread(target=task)
t.start()
t.join()
print(money) # 999
守护线程
无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行。
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。
详细解释
1.主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。
2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都被回收,而进程必须保证非守护线程都运行完毕后才能结束。
守护线程例子1
from threading import Thread,current_thread
import time
def task(i):
print(current_thread().name)
time.sleep(i)
print("GV")
for i in range(3):
t = Thread(target=task,args=(i,))
t.start()
t = Thread(target=task,args=(1,))
t.daemon = True
t.start()
print("主")
print(t.is_alive())
"""
Thread-1
GV
Thread-2
Thread-3
Thread-4
主 # 执行到这里,主进程必须等待非守护进程执行完毕才能结束
True
GV
GV
GV
"""
守护线程例子2
from threading import Thread
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main------")
"""
123
456main------
end123
end456
"""
同步锁
多个资源抢占资源的情况
from threading import Thread
import os,time
def task():
global n
temp = n
time.sleep(0.1)
n = temp - 1
if __name__ == '__main__':
n = 100
l = []
for i in range(100):
p = Thread(target=task)
l.append(p)
p.start()
for p in l:
p.join() # 主线程等待子线程运行我完毕后执行
print(n) # 99
对公共数据的操作
import threading
R = threading.Lock()
R.acquire()
"""
对公共数据的操作
"""
R.release()
同步锁的引用
将线程由原来的并发执行变成了串行,牺牲了执行效率保证了数据安全。
from threading import Thread,Lock
import time
n = 100
def task(mutex):
global n
# 加锁的代码变成串行运行
mutex.acquire()
tmp = n
time.sleep(0.1)
n = tmp - 1
mutex.release()
t_list = []
mutex = Lock()
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join() # 主线程等待子线程运行我完毕后执行
print(n) # 0
互斥锁和join的区别
#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
global n
print('%s is running' %current_thread().getName())
temp=n
time.sleep(0.5)
n=temp-1
if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))
'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''
#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread,Thread,Lock
import os,time
def task():
#未加锁的代码并发运行
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
#加锁的代码串行运行
lock.acquire()
temp=n
time.sleep(0.5)
n=temp-1
lock.release()
if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))
'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''
# 有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
# 没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
# start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
# 单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
temp=n
time.sleep(0.5)
n=temp-1
if __name__ == '__main__':
n=100
lock=Lock()
start_time=time.time()
for i in range(100):
t=Thread(target=task)
t.start()
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))
'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''
)
Thread类的其他方法
Thread实例对象的方法:
isAlive()
:返回线程是否活动的。getName()
:返回线程名。setName()
:设置线程名。
threading模块提供的一些方法:
threading.currentThread()
:返回当前的线程变量。threading.enumerate()
:返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。threading.activeCount()
:返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
代码示例
from threading import Thread
import threading
from multiprocessing import Process
import os
def work():
import time
time.sleep(3)
print(threading.current_thread().getName())
if __name__ == '__main__':
# 在主进程下开启线程
t=Thread(target=work)
t.start()
print(threading.current_thread().getName())
print(threading.current_thread()) # 主线程
print(threading.enumerate()) # 连同主线程在内有两个运行的线程
print(threading.active_count())
print('主线程/主进程')
'''
打印结果:
MainThread
<_MainThread(MainThread, started 140735268892672)>
[<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]
主线程/主进程
Thread-1
'''
join方法
from threading import Thread
import time
def sayhi(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=sayhi,args=('nick',))
t.start()
t.join()
print('主线程')
print(t.is_alive())
'''
nick say hello
主线程
False
'''
多线程实现socket
服务端
import multiprocessing
import threading
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)
def action(conn):
while True:
data=conn.recv(1024)
print(data)
conn.send(data.upper())
if __name__ == '__main__':
while True:
conn,addr=s.accept()
p=threading.Thread(target=action,args=(conn,))
p.start()
客户端
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',8080))
while True:
msg=input('>>: ').strip()
if not msg:continue
s.send(msg.encode('utf-8'))
data=s.recv(1024)
print(data
Never,Never,Never give up.