1、通过继承Thread的类完成创建线程
上一节对于多线程的使用是一个最基本的使用,但是如果我们在代码中遇到了比较复杂的多线程任务,就很难满足我们的需求,这里就需要通过继承Thread的类来完成线程的创建
实例:
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(4):
time.sleep(1)
# name属性中保存的是当前线程的名字
msg = '我是' + self.name + '@' +str(i)
print(msg)
if __name__ == '__main__':
t = MyThread()
# 注意,这里一定要使用`start`,他会默认去调用线程类的run方法
t.start()
运行结果:
我是Thread-1@0
我是Thread-1@1
我是Thread-1@2
我是Thread-1@3
上面是单线程的延时,如果是多线程,这里需要注意一点,多线程的创建一定是在线程类里面进行,在run方法进行调用,切记不可在主函数调用,不然仅仅是执行了一次普通的方法而已。
多线程实例:
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(4):
time.sleep(1)
# name属性中保存的是当前线程的名字
msg = '我是' + self.name + '@' +str(i)
self.login()
print(msg)
def login(self):
for i in range(3):
print('我登录了' + str(i) + '次')
if __name__ == '__main__':
t = MyThread()
t.start()
这样就完成了多线程的创建和使用。
运行结果
我登录了0次
我登录了1次
我登录了2次
我是Thread-1@0
我登录了0次
我登录了1次
我登录了2次
我是Thread-1@1
我登录了0次
我登录了1次
我登录了2次
我是Thread-1@2
我登录了0次
我登录了1次
我登录了2次
我是Thread-1@3
2、多线程共享全局变量
这里我们需要注意一点,在一个函数中,对全局变量进行修改的时候,到底是否使用global进行说明,需要看 是否对 全局变量的执行指向进行了修改
- 如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用global
- 如果,仅仅修改了指向空间的数据,此时不必用global
在多线程里面,就会共享全局变量。
原因:一般多线程用来处理一个任务同时需要进行的操作,这个时候对象为同一个目标任务,所以一般需要对其中的变量进行共享
实例:
import threading
import time
nums = 100
def test1():
global nums
nums += 1
print('-----test1-----=%d' % nums)
def test2():
print('------test2----=%d' % nums)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print('-----in main-----=%d' % nums)
if __name__ == '__main__':
main()
运行结果:
-----test1-----=101
------test2----=101
-----in main-----=101
可以看到我们,在test2
中并没有使用global
进行声明,他同样打印出了经过·test1·处理过的变量,最终main
函数也是,可以知道,在多线程中,是共享成员变量的
3、多线程共享全局变量——args传参
我们在进行线程的调用和执行时,是可以携带参数进行传递的
实例:
import threading
import time
def test1(temp):
temp.append(10)
print('----test1----=%s' % str(temp))
def test2(temp):
print('----test2----=%s' % str(temp))
nums = [10, 20]
def main():
# 注意,这里传递的参数必须是一个元组
t1 = threading.Thread(target=test1, args=(nums,))
t2 = threading.Thread(target=test2, args=(nums,))
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print('-----in main-----=%s' % str(nums))
if __name__ == '__main__':
main()
运行结果:
----test1----=[10, 20, 10]
----test2----=[10, 20, 10]
-----in main-----=[10, 20, 10]
可以看到,参数已经成功进行传递,并且nums
变量也为共享了全局变量
4、全局变量共享的问题
这里存在一个非常大的问题,我们先用一个实例来认识一下
实例
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print('----test1----=%d' % g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print('----test2----=%d' % g_num)
def main():
t1 = threading.Thread(target=test1, args=(10000000,))
t2 = threading.Thread(target=test2, args=(10000000,))
t1.start()
t2.start()
time.sleep(5)
print('-----in main-----=%d' % g_num)
if __name__ == '__main__':
main()
运行结果:
----test1----=11474471
----test2----=11839490
-----in main-----=11839490
可以看到,按理来说,程序中,test1=10000000,test2=10000000,最终等于20000000,可是运行结果却不是这个样子。这里就是存在的问题。来看下下面这张图。
我们在执行程序的时候,可能看起来是一个操作,但是cpu会将它分解为非常基础的操作去执行。如我们程序中的 g_num+=1
可以从图中看出分成了三个基础步骤,而cpu在执行中,会将两个线程中的步骤进行分别执行,这就导致了,执行不一致的情况。最终,导致并没有得到我们所需的结果。
解决方法:(线程同步)
- 系统调用 t1 ,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num
- t1 对g_num的值进行+1
- t1 解锁,此时g_num的值为1,其他线程就可以使用g_num了,而且此时g_num的值不是0,而是1
- 同理其他线程在对g_num进行修改时,都要先上锁,处理完后在解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性。
拓展
这里引入了一个概念(同步),同步就是协同步调,按照预订的先后次序进行运行。比如我们这里的线程同步,就是将线程A和B一块配合,A执行到一定程度时要依靠B的某种结果,于是停下来,示意B运行,B执行后,在将结果给A,A再操作。而不是A和B互相随便插入cpu进行操作
具体在实际中的操作,请见下一节的互斥锁和死锁