参考:
https://www.cnblogs.com/aylin/p/5601969.html
https://www.cnblogs.com/huxi/archive/2010/06/26/1765808.html
https://docs.python.org/zh-cn/3.8/library/threading.html
"""
threading模块的使用:
# # cpython解释器运行程序文件的时候会产生一个主线程
# # 主线程执行到threading.Thread()的时候不会产生线程
# # 线程对象不是线程,只是一个实例对象而已
# # 执行到t1.start()的时候才会创建一个子线程,并且绑定任务和运行绑定的任务
# # 三个线程在一个时间段内快速轮流执行——>并发
# # 一般直到子线程任务执行完毕之后主线程才会释放
"""
"""
单线程代码示例:注意耗时
"""
# import time
#
# def print_hello():
# print('hello')
# time.sleep(3)
#
# if __name__ == '__main__':
# start_time = time.time()
# print_hello()
# end_time = time.time()
# print(f'总用时{end_time-start_time}')
"""
多线程代码示例:注意耗时,与上方几乎一致
"""
# import time
# import threading
#
# def print_hello():
# print('你好')
# time.sleep(2)
#
# def print_bad():
# print('不好')
# time.sleep(3)
#
# if __name__ == '__main__':
# start_time = time.time()
# t1 = threading.Thread(target=print_hello)
# t2 = threading.Thread(target=print_bad)
# t1.start()
# t2.start()
# t2.join()
# end_time = time.time()
# print(f'总用时{end_time-start_time}')
"""
主线程任务执行完以后,会等待所有子线程任务执行完才会退出
"""
# import threading
# from time import sleep, ctime
#
# def sing():
# sleep(2)
# print('正在唱歌')
#
# def dance():
# sleep(3)
# print('正在跳舞')
#
# if __name__ == '__main__': # 主线程
# print(f'程序开始:{ctime()}')
# t1 = threading.Thread(target=sing) # 创建子线程对象,target参数用于绑定线程任务(可调用对象);deamon参数用于设置守护线程;args\kwargs参数用于给线程任务传参
# t2 = threading.Thread(target=dance) # 执行到这里还是只有一个主线程
# print(threading.enumerate(), '@'*20) # enumerate()以列表形式返回当前所有存活的 Thread 对象
# t1.start() # 调用线程对象的start()方法间接开启线程,该方法会安排对象的run()方法在一个独立的控制线程中被调用以运行线程任务。此处主线程不会等待子线程执行完毕,而是继续往下执行
# print(threading.enumerate())
# t2.start()
# # sleep(5) # 睡眠与否,打印结果有较大区别
# # print(threading.enumerate())
# print(f'程序结束:{ctime()}')
# while True:
# length = len(threading.enumerate()) # len(threading.enumerate())等同于threading.activeCount()返回正在运行的线程数量
# print(f'当前线程个数{length}')
# if length <= 1:
# break
# sleep(0.5)
"""
使用面向对象的方式创建线程
"""
# import time
# import threading
#
# class Mythread(threading.Thread):
# # def __init__(self):
# # super(Mythread, self).__init__() # 如果这里要实现__init__方法,需要重载
#
# def run(self) -> None: # run方法专门用来运行绑定的任务(当执行t.start()时,就会自动调用run方法),此处相当于重写父类的run方法
# for i in range(3):
# time.sleep(1)
# print('help')
#
# t = Mythread()
# t.start()
"""
线程的执行顺序是无序的,线程之间是随机切换的,这是由操作系统决定的,不是由解释器决定的
"""
import time
# import threading
#
# class Mythread(threading.Thread):
# def run(self):
# for i in range(3):
# time.sleep(1)
# msg = '我是' + self.name + ' @ ' + str(i) # 此处的name属性表示当前线程名称,该属性继承自父类,可以改动
# print(msg)
#
# def test():
# for i in range(2):
# t = Mythread() # 线程名称改动Mythread(name='线程1')
# t.start()
# print(t.name, '@@@@@@@@@')
#
# if __name__ == '__main__':
# test()
# ----------------------------多线程共享全局变量-----------------------------
"""
数字类型共享:需要声明global
"""
# import threading
# g_num = 100
#
# def work_1():
# global g_num
# for i in range(3):
# g_num += 1
# print(f"子线程1中计算得出的值为: {g_num}")
#
# def work_2():
# global g_num
# g_num += 1
# print(f'子线程2中获取到的值为: {g_num}')
#
# if __name__ == '__main__':
# print(f'子线程未启动之前主线程获取的值为: {g_num}') # 子线程启动之前获取全局变量
# t1 = threading.Thread(target=work_1)
# t1.start()
# # time.sleep(0.1) # 通过延迟保证t1线程中的任务执行完成,但推荐用下一行的代码
# t1.join() # 子线程对象调用join(timeout=None)方法,主线程会等待这个子线程绑定的任务执行完成,才会继续往下执行
#
# t2 = threading.Thread(target=work_2)
# t2.start()
# t2.join()
# print(f'主线程最终获取到的值为: {g_num}')
"""
序列类型共享,不需要声明global
"""
# import threading
# g_nums = [11, 22, 33]
#
# def work_1(nums):
# nums.append(44)
# print(f'子线程1添加完成之后的列表为: {nums}')
#
# def work_2(nums):
# print(f'子线程2获取到的列表为: {nums}')
#
# if __name__ == '__main__':
# print(f'原列表为: {g_nums}')
# t1 = threading.Thread(target=work_1, args=(g_nums,))
# t1.start()
# t1.join()
# t2 = threading.Thread(target=work_2, args=(g_nums,))
# t2.start()
# t2.join()
# print(f'主线程获取到的全局列表为: {g_nums}')
# ----------------------------多线程共享全局变量可能遇到的问题-----------------------------
"""
问题:
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而导致数据结果不正确:线程之间的切换并不是等某个线程的整个任务执行完毕才切换
例如:两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各自对g_num加10次,g_num的最终的结果理应为20。但是由于是多线程同时操作,有可能出现下面情况:
1. 在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
2. 然后t2对得到的值进行加1并赋给g_num,使得g_num=1
3. 然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
4. 这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
"""
# import threading
# import time
#
# g_num = 0
#
# def work_1(num):
# global g_num
# for i in range(num):
# g_num += 1
# print(f'线程1计算的结果是:{g_num}')
#
# def work_2(num):
# global g_num
# for i in range(num):
# g_num += 1
# print(f'线程2计算的将结果是:{g_num}')
#
# if __name__ == '__main__':
# print(f'线程创建之前g_num的值为:{g_num}')
# t1 = threading.Thread(target=work_1, args=(1000000,))
# t1.start()
#
# t2 = threading.Thread(target=work_2, args=(1000000,))
# t2.start()
#
# while len(threading.enumerate()) != 1: # 等待子线程任务执行完毕之后再执行主线程代码
# time.sleep(1)
# print(f'两个线程对同一个全局变量操作之后的g_num的值为:{g_num}') # 输出结果不是2000000
"""
解决方案:互斥锁
锁的好处:
- 确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:
- 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
- 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
"""
# import threading
# import time
#
# mutex = threading.Lock() # 创建一个互斥锁对象,默认是未上锁的状态
# g_num = 0
#
# def work_1(num):
# global g_num
# for i in range(num):
# mutex.acquire() # 上锁
# g_num += 1
# mutex.release() # 解锁
# print(f'线程1计算的结果是:{g_num}')
#
# def work_2(num):
# global g_num
# for i in range(num):
# mutex.acquire()
# g_num += 1
# mutex.release()
# print(f'线程2计算的将结果是:{g_num}')
#
# if __name__ == '__main__':
# print(f'线程创建之前g_num的值为:{g_num}')
# t1 = threading.Thread(target=work_1, args=(1000000,))
# t1.start()
#
# t2 = threading.Thread(target=work_2, args=(1000000,))
# t2.start()
#
# while len(threading.enumerate()) != 1:
# time.sleep(1)
# print(f'两个线程对同一个全局变量操作之后的g_num的值为:{g_num}') # 输出结果是2000000
"""
死锁
"""
import threading
import time
mutexA = threading.Lock()
mutexB = threading.Lock()
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()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()