前言:本文是学习网易微专业的《python全栈工程师》 中的《服务器运维开发工程师》专题的课程笔记,欢迎学习交流。同时感谢老师们的精彩传授!
一、课程目标
- 多线程概念
- 多线程实现
- 多线程
server socket
二、详情解读
2.1.什么是多线程
什么是进程?
我们每运行一个程序,就是创建一个进程。每个进程运行的时候会向系统申请资源:内存空间,cpu
时间,进程之间的资源相互独立
什么是线程?
当一个进程运行的时候,它内部就会存在一个主线程。线程就是程序内部需要完成的任务。在只有一个线程的情况,这些任务需要按照次序,逐个完成。所有线程共享进程资源。
单线程的问题:
由于一个线程的情况,这些任务需要按照次序,逐个完成,所以当其中一个任务耗时比较长,那么其他任务就需要等待,这样就浪费了等待时间
多线程:
将进程中的多个任务独立开来运行,就是多线程。
第一次列表页面爬取后,提取内容列表任务变可以在第二次列表页面爬取期间完成,这样就会提高运行效率。
2.1.1.threading
模块
多线程不代表一定同时执行。 多线程可以串行,也可以并行。
2.2.threading
模块
from threading import Threading, active_count, current_thread, local
t = Thread(target=func, args=(param1, param2), kwargs={})
t.start() # 开始运行该线程
t.join() # 其他线程必须等待此线程完成,才能运行
说明:
1).Thread
表示线程对象,通过该对象创建线程
2).t
为线程实例
3).active_count
- 函数,返回当前活动线程数
4).current_thread
- 函数,当前该线程
5).local
- 函数,线程局部变量
6).Lock
- 锁对象
示例代码如下:
from threading import Thread
from random import randint
from time import sleep,time
def myprint(tid):
sleep(randint(0,3))
print("threading:",tid)
def main():
for i in range(10):
t = Thread(target=myprint,args=(i,))
t.start()
# t.join()
if __name__ == "__main__":
main()
print("main stop....")
运行结果:
上面的程序,如果在t.start()
后面添加t.join()
,则每个线程都会等待前面的线程结束才会执行,结果如下图:
接着,测试多线程与非多线程的时间差别:
线程按照顺序执行:
# -*- coding=utf-8 -*-
from threading import Thread
from random import randint
from time import sleep, time
def myprint(tid):
sleep(randint(0, 3))
print('threading: ', tid)
def main():
for i in range(10):
t = Thread(target=myprint, args=(i, ))
t.start()
t.join()
if __name__ == '__main__':
start_time = time()
main()
print('main stop...')
end_time = time()
print(end_time - start_time)
多线程的运行:
# -*- coding=utf-8 -*-
from threading import Thread
from random import randint
from time import sleep, time
def myprint(tid):
sleep(randint(0, 3))
print('threading: ', tid)
threads = []
def main():
for i in range(10):
# print('create', i)
t = Thread(target=myprint, args=(i, ))
t.start()
threads.append(t)
if __name__ == '__main__':
start_time = time()
main()
print('main stop...')
# 检查每一个线程是否执行完毕
for i in threads:
i.join()
end_time = time()
print(end_time - start_time)
通过上面的测试,可以看出,多线程可以大大提高程序的执行效率。
消费者与生产者示例代码:
# -*- coding: utf-8 -*-
from threading import Thread, current_thread
from time import sleep
from random import randint
from queue import Queue
q = Queue(20)
def put_data():
while True:
n = randint(0,3)
sleep(n)
q.put(n)
print(current_thread(), "向队列写入>>:",n)
def get_data():
while True:
data = q.get()
print(current_thread(), "从队列读取>>", data)
def main():
t1 = Thread(target=put_data)
t2 = Thread(target=get_data)
t1.start()
t2.start()
if __name__ == "__main__":
main()
local
线程局部变量:
线程局部对象可以被所有线程访问,比如local_data
,可以被所有线程访问,但是线程局部对象的属性只能被定义该属性的线程自身访问,比如local_data
的data
属性,只能被自己的线程访问与修改,即使所有的线程中都包含同名的local_data.data
属性,因此这个data
实际上只是线程内部的局部变量,借助线性局部对象可以简化代码逻辑,保持线程间变量隔离。
示例代码:
# -*- coding=utf-8 -*-
from threading import Thread, current_thread, local
from random import randint
from time import sleep
local_data = local()
data = 0
def set_data():
local_data.data = randint(0,100)
print(current_thread().name+"::data="+str(local_data.data))
sleep(randint(0,3))
print(current_thread().name+":: after_sleep::data="+str(local_data.data))
# local_data.data = data
out()
def out():
print(local_data.data)
def main():
for i in range(3):
t1 = Thread(target=set_data)
t1.start()
if __name__ == "__main__":
main()
执行结果:
2.3.线程锁
所有线程可以共享一个进程内的资源,如果同时操作某一个对象,就可能发生混乱,这时候可以通过加锁解决。
线程锁通过Lock
对象实例实现
l = Lock()
包含两个实例方法:
l.acquire() # 加锁
l.release() # 解锁
示例代码:
# -*- coding: utf-8 -*-
from threading import Thread, Lock, current_thread
from time import sleep
from random import randint
lock = Lock()
number = [0]
def loop(i):
lock.acquire()
try:
if i > number[-1]:
sleep(randint(0, 5))
number.append(i)
except:
pass
finally:
print("thread %s ended." % current_thread().name, number, "\r\n")
lock.release()
pass
if __name__ == "__main__":
for i in range(10):
t = Thread(target=loop, args=(i,))
t.start()
执行结果:
2.4.多线程socket_server
实现
2.4.1.单线程socket_server
问题
代码演示:
服务端代码:
from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
s_server = socket(AF_INET, SOCK_STREAM)
s_server.bind(("127.0.0.1", 8000))
s_server.listen()
def chat(client):
while True:
try:
message = client.recv(1024)
except:
break
else:
print(message.decode("utf-8"))
# 空字符串跳过
if not message:
continue
if message == "quit":
print("close")
client.close()
break
else:
print("send")
client.send(b"\r\nrecived...")
while True:
print("wait......")
s_client, addr = s_server.accept()
t = Thread(target=chat, args=(s_client,))
t.start()
客户端代码:
# -*- coding: utf-8 -*-
from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
s_client = socket(AF_INET, SOCK_STREAM)
s_client.connect(("127.0.0.1", 8000))
def chat():
print("wait...")
while True:
message = input("请输入消息:")
if not message:
continue
s_client.send(bytes(message, encoding="utf-8"))
if message == "quit":
break
s_client.close()
print("close()")
def get_message():
while True:
try:
message = s_client.recv(1024)
print(message.decode())
except Exception as e:
print(e)
break
finally:
pass
t = Thread(target=chat)
t.start()
t = Thread(target=get_message)
t.start()
三、课程小结
- 01
threading
模块 - 02 服务器端
- 03 客户端
线程开发要注意变量之间的操作,防止变量互相干扰。
方法有两种:
1.如果变量只是对线程内部有效,可以使用线程局部变量。
2.如果变量需要多个线程共享共同操作,可以加锁