活动地址:CSDN21天学习挑战赛
学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…
目录
一、多线程
01.没有多任务的程序
首先通过一个案例进行引入,如下没有多线程的时候:
import time
def sing():
"""唱歌5秒钟"""
for i in range(5):
print("---正在唱歌---")
time.sleep(1)
def dance():
"""唱歌5秒钟"""
for i in range(5):
print("---正在跳舞---")
time.sleep(1)
def main():
dance()
sing()
if __name__ == "__main__":
main()
问:如何实现跳舞和唱歌同时进行?
02.初始多任务,创建多任务第一种方式
import time
import threading
def sing():
"""唱歌5秒钟"""
for i in range(5):
print("---正在唱歌---")
time.sleep(1)
def dance():
"""唱歌5秒钟"""
for i in range(5):
print("---正在跳舞---")
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == "__main__":
main()
# 多任务:操作系统同时运行多个任务
# 并行:真的多任务每个程序一个cpu
# 并发:假的多任务cpu数量<任务数量
# 一个程序运行起来后一定有一个执行代码的东西这个东西就称之为线程
# 查看一共有多少个线程:threading.enumerate()的返回元素有几个就有几个线程
知识讲解:
并发:假的多任务cpu数量小于程序数量相当于一个cup运行的速度很快,于是便由cup来调度一会执行这个,另一会执行另一个。
并行:真的多任务一个程序占用一个cup
线程:一个程序运行起来后一定有一个执行代码的东西这个东西我们把他叫做线程
03.查看线程数
1>enumerate知识点复习
a = [1, 2, 3, 4]
for i in enumerate(a):
print(i)
# (0, 1)
# (1, 2)
# (2, 3)
# (3, 4)
for i, j in enumerate(a):
print(i, j)
# 0 1
# 1 2
# 2 3
# 3 4
enumerate()函数应用于对列表的下标和对应的值进行分离,当你遍历enumerate(列表)时返回每一个(下标,值)的元组,同时enumerate自动会判断取完值了没,取完了会自动结束。
2>查看线程数
import threading
import time
def test1():
for i in range(5):
print("---test1---%s" % i)
def test2():
for i in range(5):
print("---test2---%s" % i)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print(threading.enumerate())
if __name__ == "__main__":
main()
# 1.没有加time.sleep(1)线程的运行是随机的,本代码有三个线程随机争抢线程资源最后的结果每次不一样
# 2.加了time.sleep(1)到了第18行代码时创建了子线程然而此时主线程要休眠故资源全部在子线程全部完成了子线程再说
3>让某些线程先执行
import threading
import time
def test1():
for i in range(5):
print("---test1---%s" % i)
time.sleep(1)
def test2():
for i in range(5):
print("---test2---%s" % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=test1) # 仅仅创建一共普通的对象,不会创建线程
t2 = threading.Thread(target=test2) # 如果创建Thread时执行的函数结束,意味着这个子线程结束了
t1.start() # 子线程的创建为此时。子线程的真正执行是调用start()时开始的。
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate()) == 1:
break
time.sleep(1)
# 程序结束一定是主线程结束了
if __name__ == "__main__":
main()
# [<_MainThread(MainThread, started 23628)>,
# <Thread(Thread-1 (test1), started 25304)>,
# <Thread(Thread-2 (test2), started 1336)>]
04.对第一种创建多线程的方式的总结
第一种创建多线程是在函数层面上的总的来说为一下步骤:
1.导入threading模块
2.创建对象t1=threading.Thread(target=函数名字) # 注意只是函数名字,不要"函数名字()",其次要注意的点是此时只可以算是资源准备的阶段并没有创建好子线程(子线程还不可以进入函数执行代码)
3.t1.stare() # 此时子线程就可以进入函数分别执行代码也就是所说的创建了子线程
4.为了方便我们分析我们可以说他们是一起执行的,可以说创建了子线程后,线程之间各走各的互不影响。但是实际上我们要明白多线程实现的多任务只是函数之间互相争夺资源(cup随机分配)使得我们看起来他们就好像是一起执行的。
05.创建多线程的第二种方式---将线程封装成类
import threading
import time
class MyThread(threading.Thread): # 继承
def run(self): # 会被自动调用注意函数名一定要是run()
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) # name属性中保存的是当前线程的名字
print(msg)
if __name__ == '__main__':
t = MyThread() # 创建对象创建一个对象后面只能有一个子线程
t.start() # 调用start()方法这里面会自动调用run()方法,里面写什么就子线程执行什么
"""
适用于用一个线程去做一个事情而这个事情要调用多个函数去完成这个时候在
类中封装多个函数当调用start()方法时自动调用run()方法
可以把其他函数的调用也都写入run方法中
"""
06.复习在函数里面修改全局变量
问:在一个函数中,对全局变量进行修改的时候,到底是否需要使用global进行说明,什么时候要加全局变量说明?
答:关键点要看原变量的指向有无改变。
对于列表而言定义a=[1, 2, 3]在函数实现a.append(5)此时a依旧是指向原来的内存地址,只是在原来的基础上在原来值的基础上加了一个元素。
而对于a=10这样的赋值而言,在函数内部进行修改a=20此时相当于本来a是指向内存里面10这个值的,然后经过你这一改把a由原来的内存指向10改为了内存指向20此时如果没有声明global就会在函数内部创建局部变量,如果声明了才会更改全局变量。
a = [1, 2, 3]
num = 100
def x():
global num
num += 100
a.append(1)
print(a, num) # [1, 2, 3] 100
x()
print(a, num) # [1, 2, 3, 1] 200
# 总结对于内存箭头指向没有变的全局变量不要加global对于变了的要加global
"""
在一个函数中,对全局变量进行修改的时候,到底是否需要使用global进行说明
要看是否对全局变量的执行指向进行了修改
如果修改了指向,即让全局变量指向了一个新的地方,那么必须使用global
如果是修改了指向的中间的数据,此时不用必须使用global
"""
07.多线程共享全局变量
import threading
import time
# 定义一个全局变量
g_num = 100
def test1():
global g_num
g_num += 1
print("---in test1 g_num=%d---" % g_num)
def test2():
print("---in test2 g_num=%d---" % g_num)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1)
t2.start()
print("---in main Thread g_num=%d---" % g_num)
if __name__ == "__main__":
main()
"""
---in test1 g_num=101---
---in test2 g_num=101---
---in main Thread g_num=101---
"""
08.传入实参
import threading
import time
# 定义一个全局变量
g_num = [11, 22]
def test1(temp):
temp.append(33)
print("---in test1 temp=%s---" % temp)
def test2(temp):
print("---in test2 temp=%s---" % temp)
def main():
# target指定线程去哪个函数执行线程,args指定调用函数时传递哪些数据过去
t1 = threading.Thread(target=test1, args=(g_num,))
t2 = threading.Thread(target=test2, args=(g_num,))
t1.start()
time.sleep(1)
t2.start()
print("---in main Thread g_num=%s---" % g_num)
if __name__ == "__main__":
main()
"""
---in test1 temp=[11, 22, 33]---
---in test2 temp=[11, 22, 33]---
---in main Thread g_num=[11, 22, 33]---
总结:args()里面传递一个元组
"""
09. 共享全局变量存在的问题
大家想想既然共享全局变量,那么如果两个线程修改同一个变量的值时如:a=1遇到了其他两个线程都是使a加一,把这些步骤拆开可以分为一下步骤:
1.取a的值
2.修改a的值
3.保存a的值
此时又因为两个子线程争夺 cup资源就会出现如下问题:
当第一个线程执行了1和2步骤使a的值为2了,就要保存时,cup把资源给了第二个线程了,让第二个线程对a进行加1操作使其执行123步骤结果a=2,然后再把资源给线程一此时线程一再执行上一步没执行的步骤三结果还是a=2.由此我本来的想法是把a加了两次变成了3,而偏偏因为这个原因使a只加了一次结果为2.
实验:
# 产生资源竞争
import threading
import time
# 定义一个全局变量
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print("---in test1 g_num=%s---" % g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print("---in test2 g_num=%s---" % g_num)
def main():
# target指定线程去哪个函数执行线程,args指定调用函数时传递哪些数据过去
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(9000000,))
t1.start()
t2.start()
while True:
if len(threading.enumerate()) == 1:
print("----in main g_num=%s----" % g_num)
break
if __name__ == "__main__":
main()
10.互斥锁解决问题
相当于生活中的上厕所,当你在上厕所时将门锁起来,这样资源会等到你完成了你要做的事情才会被别人利用,即要么就不做,要做就把他做完。
# 要么不做要么全部做完:同步的概念就是协调------互斥锁
# 产生资源竞争
# 产生资源竞争
import threading
import time
# 定义一个全局变量
g_num = 0
# 创建互斥锁:
mutex = threading.Lock()
def test1(num):
global g_num
for i in range(num):
mutex.acquire()
g_num += 1
mutex.release()
print("---in test1 g_num=%s---" % g_num)
def test2(num):
global g_num
for i in range(num):
mutex.acquire()
g_num += 1
mutex.release()
print("---in test2 g_num=%s---" % g_num)
def main():
# target指定线程去哪个函数执行线程,args指定调用函数时传递哪些数据过去
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(9000000,))
t1.start()
t2.start()
while True:
if len(threading.enumerate()) == 1:
print("---in main Thread g_num=%s---" % g_num)
break
if __name__ == "__main__":
main()
"""
创建互斥锁全局变量:mutex = threading.Lock()
锁定:mutex.acquire()
释放:mutex.release()
"""
创建互斥锁全局变量:mutex = threading.Lock()
锁定:mutex.acquire()
释放:mutex.release()
11.互斥锁带来的问题
通俗的话来讲就是:和生活中的尝试引用,比如一对情侣吵架了,男的认为自己有理,女的认为塔有理,于是互相等待对方的道歉,如果其中一个到了一定条件就道歉那事情完美解决。如果他们都不道歉还一直等待对方的道歉那么这个事情就永远解决不了。
# 死锁:都在等对方的事情,多任务中1方等待2方的资源,而偏偏2方也在等1方资源
import threading
import time
class MyThread1(threading.Thread):
def run(self):
# 对mutexA上锁
mutexA.acquire()
# mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
print(self.name+'----do1---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
mutexB.acquire()
print(self.name+'----do1---down----')
mutexB.release()
# 对mutexA解锁
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
# 对mutexB上锁
mutexB.acquire()
# mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
print(self.name+'----do2---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
mutexA.acquire()
print(self.name+'----do2---down----')
mutexA.release()
# 对mutexB解锁
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
"""
Thread-1----do1---up----
Thread-2----do2---up----
一直卡在这
解决方法:添加超时时间
"""
这个案例的关键是对于MyThread1来说一开始就把A上锁,而MyThread2一开始把B上锁,而MyThread1中的A解锁的条件是对B上锁,而B已经被MyThread2上锁了此时只能等待其解锁。而对于MyThread2来说解锁B的条件是对A上锁,而A已经被MyThread1上锁了,此时MyThread2只能等待。所以他们就一直等待对方.......哈哈哈哈有点绕
12.多任务版聊天器
本段相当于http://t.csdn.cn/a4L9E的拓展看看就行
import socket
import threading
def recv(udp_socket):
while True:
recv_data = udp_socket.recvfrom(1024)
data = recv_data[0].decode("utf-8")
dist_ip = recv_data[1][0]
dist_port = recv_data[1][1]
print("\n接收到ip:{:},port:{:}, 发来消息:{:}".format(dist_ip, dist_port, data))
if data == "exit":
print("\n检测到对方已退出发送界面,后台接收退出...")
break
def send(udp_socket):
while True:
list1 = """
-----请选择功能-----
1.发送消息
2.退出程序"""
print(list1)
a = input("请选择需要的功能:")
if a == "1":
dist_ip = input("请输入对方ip:")
dist_port = input("请输入对方port:")
print("-----欢迎来到发送消息界面-----")
while True:
msg = input("请输入要发送的消息:")
udp_socket.sendto(msg.encode('utf-8'), (dist_ip, int(dist_port)))
if msg == "exit":
print("正在退出发送消息界面....")
break
elif a == "2":
break
else:
print("请输入正确的序号!")
def main():
while True:
# 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip = input("请输入本机ip:")
port = input("请输入本机port:")
try:
udp_socket.bind((ip, int(port)))
break
except:
print("输入错误端口可能占用请重新输入")
# 接收数据模块
t1 = threading.Thread(target=recv, args=(udp_socket,))
t1.start()
send(udp_socket)
# 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
二、总结
有一说一写博客进行总结,对个人来说也是一个很大的提高它使我对以前的知识点更加的升华。希望他也可以帮助到你。
让我们追寻光,成为光,发散光。加油加油!