1、线程创建
创建线程有两种方式,一种是直接使用threading模块中的类进行创建,另一种是继承threading模块的类写一个类来对进程进行创建。
threading.Thread(target=None, name=None, args=(), kwargs={})
target 指要创建的线程的方法名,name 指给此线程命名,命名后可以调用 threading.current_thread().name 方法输出该线程的名字, args/kwargs 指 target 指向的方法需要传递的参数,必须是元组形式,如果只有一个参数,需要以添加逗号。
方法一:
import threading
import time
def job1(num):
while True:
num += 2
print('{} is running >> {}'.format(threading.current_thread().name, num))
time.sleep(2)
new_job = threading.Thread(target=job1, name='Add2', args=(100,))
new_job.start()
n = 1
while True:
n += 1
print('{} is running >> {}'.format(threading.current_thread().name, n))
time.sleep(1)
方法二:
import threading
import time
class MyThread(threading.Thread):
def __init__(self, n):
super().__init__() #必须调用父类的初始化方法
self.n = n
def run(self) -> None:
while True:
self.n += 2
print('{} is running >> {}'.format(threading.current_thread().name, self.n))
time.sleep(2)
new_job = MyThread(100)
new_job.setName("Add2")
new_job.start()
n = 1
while True:
n += 1
print('{} is running >> {}'.format(threading.current_thread().name, n))
time.sleep(1)
输出结果:
Add2 is running >> 102
MainThread is running >> 2
MainThread is running >> 3
Add2 is running >> 104
MainThread is running >> 4
MainThread is running >> 5
Add2 is running >> 106
MainThread is running >> 6
MainThread is running >> 7
Add2 is running >> 108
MainThread is running >> 8
MainThread is running >> 9
Add2 is running >> 110
...
2、守护线程
若当前线程是守护线程,主线程结束,守护线程没有运行完,但会被强制结束;若当前线程是非守护线程,主线程只有等到非守护线程运行完毕才能结束。
import threading
import time
# 每1秒加1
def job1(num):
while num < 105:
num += 1
print('{} is running >> {}'.format(threading.current_thread().name, num))
time.sleep(1)
print("job1 Endding")
# 每2秒加2
def job2(num):
while True:
num += 2
print('{} is running >> {}'.format(threading.current_thread().name, num))
time.sleep(2)
# 线程1,一秒加一
new_job1 = threading.Thread(target=job1, name='Add1', args=(100,))
new_job1.start()
# 线程2,两秒加二
new_job2 = threading.Thread(target=job2, name='Add2', args=(1,))
# 设置为守护线程
new_job2.setDaemon(True)
new_job2.start()
# 主线程等待job1执行完成
# new_job1.join()
print('{} Ending'.format(threading.current_thread().name))
个人理解:只要当前主线程中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着主线程一同结束。
运行结果:
Add1 is running >> 101
Add2 is running >> 3
MainThread Ending
Add1 is running >> 102
Add2 is running >> 5
Add1 is running >> 103
Add1 is running >> 104
Add2 is running >> 7
Add1 is running >> 105
job1 Endding
3、阻塞线程
join()方法会使线程进入等待状态,直到调用join()方法的子线程运行结束,可通过timeout参数设定等待时间。
注意,join方法只能写在start方法之后。
在示例2的基础上,取消new_job1.join()的注释,输出结果为:
Add1 is running >> 101
Add2 is running >> 3
Add1 is running >> 102
Add1 is running >> 103
Add2 is running >> 5
Add1 is running >> 104
Add1 is running >> 105
Add2 is running >> 7
job1 Endding
MainThread Ending
4、线程锁
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的准确性,需对多个线程进行同步,确保同一时间只有一个线程可以访问被锁定的代码块,从而避免线程间的竞争条件和数据不一致的问题。
acquire()方法获取锁;release()方法释放锁;
互斥锁(Lock):
互斥锁只能开一次然后释放一次,一次开启后必须接上一次关闭。
import threading
import time
num = 0
lock = threading.Lock()
def job1():
global num
for i in range(5):
lock.acquire() # 加锁
num += 1
print("job1")
print(num)
lock.release() # 释放锁
# 上述代码也可以直接写为
# with lock:
# num += 1
new_job1 = threading.Thread(target=job1, name='Add1')
new_job1.start()
for i in range(5):
lock.acquire() # 加锁
num += 2
print("*****************")
print(num)
lock.release() # 释放锁
# 等待线程执行完毕
time.sleep(3)
print('num = {}'.format(num))
运行结果:
job1
1
job1
2
job1
3
job1
4
job1
5
*****************
7
*****************
9
*****************
11
*****************
13
*****************
15
num = 15
递归锁(RLock):
递归锁可以开多次,再进行多次释放,RLock支持大锁里套小锁。
import threading, time
def run1():
lock.acquire()
print("grab the first part data")
global num
num += 1
lock.release()
return num
def run2():
lock.acquire()
print("grab the second part data")
global num2
num2 += 1
lock.release()
return num2
def run3():
lock.acquire()
res = run1()
print('--------between run1 and run2-----')
res2 = run2()
lock.release()
print(res, res2)
if __name__ == '__main__':
num, num2 = 0, 0
lock = threading.RLock()
for i in range(3):
t = threading.Thread(target=run3)
t.start()
运行结果:
grab the first part data
--------between run1 and run2-----
grab the second part data
1 1
grab the first part data
--------between run1 and run2-----
grab the second part data
2 2
grab the first part data
--------between run1 and run2-----
grab the second part data
3 3