一、基础知识
\qquad
线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
\qquad
单核CPU在同一时刻只能运行一个线程,电脑上能同时运行多个程序,是因为CPU在运行的时候,CPU随机选取一个程序运行,然后在随机选取另一个程序运行,由于CPU每次选取程序运行的时间极短,所以认为感觉到的是多个程序一起运行。
\qquad
当任务数大于CPU核数时成为并发,当任务数大于或者等于CPU核数时,称为并行。
二、案例代码
\qquad 1) 编写程序打印5行1,5行2
import time
def test1():
for i in range(5):
print("---1---")
time.sleep(1)
def test2():
for i in range(5):
print("---2---")
time.sleep(1)
def main():
test1()
test2()
if __name__ == "__main__":
main()
\qquad 上述代码运行的结果是打印一行“—1---”后等1秒,再打印一行"—1---",这样打印5行"—1---“后,等1秒后继续打印”—2---",再等1秒钟,继续打印"—2---",直至打印5行"—2---",整个过程总共用时10秒。
\qquad 2) 编写程序,同时打印一行1和一行2,打印五次。
import time
import threading
def test1():
for i in range(5):
print("---1---")
time.sleep(1)
def test2():
for i in range(5):
print("---2---")
time.sleep(1)
def main():
#这里只生成了一个普通的对象,未生成子线程
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
#这里生成一个子线程,开始调用运行test1函数
t1.start()
#如果要让test1()先执行,然后test2()在执行可在t1生成子线程后延时
#time.sleep(1)
t2.start()
if __name__ == "__main__":
main()
\qquad 上述代码运行结果:打印一行"—1---",一行"—2---“后等1秒,继续打印一行”—1---",一行"—2---",直至程序结束,总共花时5秒。
\qquad 3) thread函数args参数的使用
import threading
import time
def test1(num1):
for i in range(num1):
print("---%d---" % i)
time.sleep(1)
def test2(num2):
for i in range(num2):
print("---%d---" % i)
time.sleep(1)
def main():
#当子线程指向的函数有形参时,threading类的Thread函数里的参数可以传递参数,需要注意的是args后面的值必须是元组
t1 = threading.Thread(target=test1,args=(5,))
t2 = threading.Thread(target=test2,args=(10,))
t1.start()
t2.start()
while True:
#循环打印当前线程
print(threading.enumerate())
time.sleep(1)
#当生成的两个子线程结束后,使得循环结束
if len(threading.enumerate())<=5:
break
if __name__ == "__main__":
main()
\qquad 4) 验证线程共享全局变量
import threading
import time
a=0
def test1(num1):
global a
for i in range(num1):
a=a+1
def test2(num2):
global a
for i in range(num2):
a=a+1
def main():
t1 = threading.Thread(target=test1,args=(100,))
t2 = threading.Thread(target=test2,args=(100,))
t1.start()
t2.start()
print(a)
if __name__ == "__main__":
main()
\qquad
代码解释:若最后的打印出a的值为200,说明多线程是共享全局变量的,若最后打印出a的值不等于200,则说明不是共享全局变量的。
\qquad
代码结果:最后打印出a的值为200
\qquad 5)验证共享全局变量的弊端
import threading
import time
a=0
def test1(num1):
global a
for i in range(num1):
a=a+1
print("--test1---%d---\n" % a)
def test2(num2):
global a
for i in range(num2):
a=a+1
print("--test2---%d---\n" % a)
def main():
t1 = threading.Thread(target=test1,args=(1000000,))
t2 = threading.Thread(target=test2,args=(1000000,))
t1.start()
t2.start()
time.sleep(1)
print(a)
if __name__ == "__main__":
main()
\qquad
代码结果及解释:最后输出的结果是test1输出a的值为1000000,test2的a的值为1386825,最后输出a的值为1386825。按照前面所说的多线程共享全局变量有出入,原因是:电脑CPU再一个时刻只能运行一个线程,且在这一时刻运行那个线程由操作系统决定。上述代码运行时,test1线程在运行加1的代码时,可能来不及将新的值赋值给变量a,CPU就已经选择运行test2的线程了。
\qquad
解决办法:采用互斥锁,当test1的线程在运行的时候,使它处于锁定状态,直至test1的一个运算全部运算完才解锁,而当test1的线程运行的时候,其他线程只能等test1的 线程解锁之后才能运行。代码如下:
import threading
import time
a=0
mutex = threading.Lock()
def test1(num1):
global a
for i in range(num1):
mutex.acquire()
a=a+1
mutex.release()
print("--test1---%d---\n" % a)
def test2(num2):
global a
for i in range(num2):
mutex.acquire()
a=a+1
mutex.release()
print("--test2---%d---\n" % a)
def main():
t1 = threading.Thread(target=test1,args=(1000000,))
t2 = threading.Thread(target=test2,args=(1000000,))
t1.start()
t2.start()
time.sleep(2)
print(a)
\qquad 代码结果及解释:代码最后的结果时test1输出a的值为1824762,而test2和最后输出的a的值都为2000000。test1输出的a的值不是1000000,test1的线程和test2的线程交互运行的结果。
\qquad 6)当有两个互斥锁的时候可能造成死锁。例子如下:
import threading
import time
a=0
mutex1 = threading.Lock()
mutex2 = threading.Lock()
class mythread1(threading.Thread):
def run(self):
mutex1.acquire()
print(self.name+"---do1--up---")
time.sleep(1)
mutex2.acquire()
print(self.name+"---do1--down--")
mutex2.release()
mutex1.release()
class mythread2(threading.Thread):
def run(self):
mutex2.acquire()
print(self.name+"---do2--up---")
time.sleep(1)
mutex1.acquire()
print(self.name+"---do2--down--")
mutex1.release()
mutex2.release()
def main():
t1 = mythread1()
t2 = mythread2()
t1.start()
t2.start()
if __name__ == "__main__":
main()
\qquad
代码结果及解释:最后只输出:“Thread-18—do1–up—”,
"Thread-19—do2–up—"没有打印出后面的"do—down—"是因为两个线程都在等着彼此运行的代码中解锁而阻塞了。
\qquad
解决办法:在代码中尽量避免死锁的形式;设置超时时间
\qquad 7) 多线程的udp聊天器
import threading
import socket
def send_data(udp_socket):
while True:
send_data1 = input("请输入你想发送的内容:")
udp_socket.sendto(send_data1.encode("utf-8"),("192.34,34,2",6789))
def recv_data(udp_socket):
while True:
udp_socket.recvfrom(1024)
def main():
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_socket.bind(("",7890))
t1 = threading.Thread(target=send_data,args=(udp_socket,))
t2 = threading.Thread(target=recv_data,args=(udp_socket,))
t1.start()
t2.start()
if __name__ == "__main__":
main()