我们在编写程序时,若我们编写的是一个爬虫程序,当下载数据较大时,那么单机爬虫就会显得力不从心,速度很慢并且效率也不高,那么应该怎么解决这个问题呢?我们可以使用多线程来解决它。
创建多线程
这里举了一个例子
import threading
import time
def sing():
for i in range(5):
print("正在唱歌...")
time.sleep(2)
def dancing():
for i in range(5):
print("正在跳舞...")
time.sleep(2)
def main():
t1 = threading.Thread(target=sing) # 创建线程对象
t2 = threading.Thread(target=dancing)
t1.start() # 执行线程
t2.start()
当我们要创建线程时,使用 threading.Thread(target=函数名) 即可,如我们想要传递参数,这个模块中还可以添加 threading.Thread(target=函数名, args=(元组))
运行程序:可以看出程序在一起执行,执行时间会缩小一倍!
循环查看线程名称
在threading模块中可以使用threading.enumerate() 查看当前程序运行时的线程的名称。
这里有一个例子:可以循环查看当前线程的运行状况。
import threading
import time
def test1(num):
for i in range(num):
print("-----test1 %d-----" % i)
time.sleep(1) # 当子线程全部结束时, 主线程才会结束,如果主线程先结束,那么子线程不会被运行
def test2(num):
for i in range(num):
print("----test2 %d -----" % i)
time.sleep(1)
def main():
# 在传入参数时,应传入一个元组类型
t1 = threading.Thread(target=test1, args=(5,))
t2 = threading.Thread(target=test2, args=(10,))
t1.start()
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate()) <= 1:
break
time.sleep(1)
if __name__ == '__main__':
main()
在类中使用多线程
如果我们是在函数中使用多线程, 那么只需要在main()方法中创建函数的多线程对象,然后调用start方法就可以了
那么怎么在类中执行多线程呢?
- 在类中使用多线程:这个class应该继承于 threading.Thread模块
- 在这个类中必须定义run方法,因为它会自动调用run方法中的内容
- 创建类对象,然后对象.start()即可启动线程
举例:
import threading
import time
class Test(threading.Thread): # 这个类需要继承自threading中的Thread模块
def run(self):
for i in range(3):
time.sleep(1)
print("------im %d=------" % i)
self.login()
self.register()
def login(self): # 如果类中有多个函数, 那么我可以把函数封装到run函数中
print("这是登录")
def register(self):
print("这是注册。。。")
if __name__ == '__main__':
# 创建类对象
t = Test()
# t会自动执行run函数中的代码,所以我们只需要调用start即可
t.start()
结果如下:
多线程共享全局变量
测试全局共享变量,在使用不可变类型是需使用global进行声明, 不可变类型: 元组、数字、字符串
这里定义了两个函数,用来测试一个数字的改变状况
num = 100
def test1(): # 定义函数test1,修改num的值
global num # 将num修改为全局变量
num += 1
print("----test1: num=%d" % num)
def test2(): # 打印num值,查看结果
print("----test2: num=%d" % num)
def main():
# 创建两个函数的线程对象
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
time.sleep(1)
运行结果:
使用互斥锁解决资源竞争的问题
当线程的运行次数过多时,那么它们之间就会产生资源竞争,导致结果不在那么准确
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print("----test1:g_num=%d" % g_num)
def test2(num):
global g_num # 因为要修改g_num的值,所以需要将它设置为全局变量
for i in range(num):
g_num += 1
print("--test2: g_num=%d" % g_num)
def main():
# 传入参数时用args参数传递一个元组
t1 = threading.Thread(target=test1, args=(1000000,)) # 当传入数据过大时,就会出现资源争夺的问题
t2 = threading.Thread(target=test2, args=(1000000,)) # 循环遍历100万次
t1.start()
t2.start()
time.sleep(3)
print("----main: g_num= %d" % g_num)
if __name__ == '__main__':
main()
正常情况下:
- tes1因该为 1000000
- test2因该为 2000000
- main因该为 2000000
但是运行结果如下:
这就产生了资源抢夺的问题。那么因该怎么解决这种问题呢?互斥锁就该闪亮登场了,
什么时互斥锁:简单来说,就是只有都当你上锁这个程序完成后,后面的程序才能继续运行。通俗的讲,就是前面一个人去上厕所,而他把门上锁了,只有当他上完厕所之后,将门打开,你才能上厕所,就是这个道理。我们就用上面这个例子,使用互斥锁它们不在资源竞争。
import threading
import time
g_num = 0
lock = threading.Lock() # 创建互斥锁
def test1(num):
global g_num
lock.acquire() # 上锁
for i in range(num):
g_num += 1
lock.release() # 当程序执行完毕时,进行释放
print("----test1:g_num=%d" % g_num)
def test2(num):
global g_num # 因为要修改g_num的值,所以需要将它设置为全局变量
with lock: # 互斥锁操作,当with内部代码块执行完毕时,它会自动释放
for i in range(num):
g_num += 1
print("--test2: g_num=%d" % g_num)
def main():
"""传入参数时用args参数传递一个元组"""
thread_list = [] # 创建线程池
t1 = threading.Thread(target=test1, args=(1000000,))
thread_list.append(t1)
t2 = threading.Thread(target=test2, args=(1000000,))
thread_list.append(t2)
for t in thread_list:
t.start()
t.join() # 让子线程先完成,主线程最后执行
print("----main: g_num= %d" % g_num)
if __name__ == '__main__':
main()
结果:
掌握了以上的内容,应该可以满足日常工作中的多线程需求了。
后面会更新多任务中的另一种方式—多进程