赞
踩
目录
线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属于一个进程的其它线程共享进程所拥有的全部资源。
当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,简而言之:程序启动就会创建一个主线程。
主线程的重要性体现在两个方面:
(1)主线程是可以产生其它子线程的线程
(2)通常它必须最后完成执行,比如执行各种关闭动作
可以看做是程序执行的一条分支,当子线程启动后会和主线程一起同时执行。
单线程:
- import time
-
- def sayHello():
- print("Hello")
- time.sleep(1)
-
-
- if __name__ == '__main__':
- for i in range(5):
- sayHello()
结果:(耗费约5s时间)
多线程:
- import time,threading
-
- def sayHello():
- print("Hello")
- time.sleep(1)
-
-
- if __name__ == '__main__':
- for i in range(5):
- # 创建子线程对象
- thread_obj = threading.Thread(target=sayHello)
-
- # 启动子线程对象
- thread_obj.start()
结果:(耗费约1s时间)
1.4 主线程会等待所有的子线程结束后才结束
- import time,threading
-
- def sing():
- for i in range(3):
- print("is singing... %d" % i)
- time.sleep(1)
-
- def dance():
- for i in range(3):
- print("is dancing... %d" % i)
- time.sleep(1)
-
-
- if __name__ == '__main__':
- print("The main thread starts to execute")
-
- t1 = threading.Thread(target=sing)
- t2 = threading.Thread(target=dance)
-
- t1.start()
- t2.start()
-
- print("Main thread execution completed")
结果:(可以看到主线程执行完毕后,在等待所有线程执行完毕后才结束运行)
使用threading.enumerate()可以获取当前所有活跃的线程对象列表,再使用len()查看活跃的线程数量。
- import time,threading
-
- def sing():
- for i in range(3):
- print("is singing... %d" % i)
- time.sleep(1)
-
- def dance():
- for i in range(3):
- print("is dancing... %d" % i)
- time.sleep(1)
-
-
- if __name__ == '__main__':
- thread_list = threading.enumerate()
- print("\nCurrent number of threads:%d" % len(thread_list))
-
- t1 = threading.Thread(target=sing)
- t2 = threading.Thread(target=dance)
-
- t1.start()
- t2.start()
-
- thread_list = threading.enumerate()
- print("\nCurrent number of threads:%d" % len(thread_list))
-
-
结果:
线程传递参数有三种方法:
1.使用元组传递:
threading.Thread(target=xxx, args=(参数1,参数2,....))
- import time,threading
-
- def test(a,b,c):
- print("a=%d,b=%d,c=%d" % (a,b,c))
- time.sleep(1)
-
-
- if __name__ == '__main__':
- for i in range(5):
- # 创建子线程对象
- thread_obj = threading.Thread(target=test, args=(10,20,30))
-
- # 启动子线程对象
- thread_obj.start()
结果:
2.使用字典传递
threading.Thread(target=xxx, kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})
- import time,threading
-
- def test(a,b,c):
- print("a=%d,b=%d,c=%d" % (a,b,c))
- time.sleep(1)
-
-
- if __name__ == '__main__':
- for i in range(5):
- # 创建子线程对象
- thread_obj = threading.Thread(target=test, kwargs={"a":10,"c":20,"b":30,})
-
- # 启动子线程对象
- thread_obj.start()
结果:
3.同时使用元组和字典
threading.Thread(target=xxx, args=(参数1,参数2,....), kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})
- import time,threading
-
- def test(a,b,c):
- print("a=%d,b=%d,c=%d" % (a,b,c))
- time.sleep(1)
-
-
- if __name__ == '__main__':
- for i in range(5):
- # 创建子线程对象
- thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
-
- # 启动子线程对象
- thread_obj.start()
结果:
如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。
- import time,threading
-
- def test(a,b,c):
- for i in range(5):
- print("正在输出:%d" % (i))
- time.sleep(1)
-
-
- if __name__ == '__main__':
-
- # 创建子线程对象
- thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
-
- # 设置线程守护:子线程守护主线程
- thread_obj.setDaemon(True)
-
- # 启动子线程对象
- thread_obj.start()
-
- time.sleep(1)
-
- print("主线程即将结束...")
-
- # 退出主线程
- exit()
结果:
其实就是操作系统轮流让各个任务交替执行,如:任务1执行0.01秒,然后任务2执行0.01秒,再让任务3执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是由于CPU执行速度非常快,我们就感觉所有任务都在同时执行一样。
并发:并发指的是任务数大于CPU核数,通过操作系统的各种调度算法,实现用多个任务“一起”执行(实际总有一些任务不执行,因为切换任务的速度相当快,看上去一起执行而已)
并行:指的是任务数小于或等于CPU核数,即任务真的是一起执行的。
真正的并行执行多任务只能在多核CPU上实现,但是,由于人物数量远远多于CPU的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行。
通过threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要以下三步:
1.让自定义类继承 threading.Thread
2.让自定义类重写run方法
3.通过实例化自定义类对象.start()方法启动自定义线程
- import threading,time
-
-
- # 自定义线程类
- class MyThread(threading.Thread):
- def __init__(self, num):
- super().__init__() # 要先调用父类的init方法否则会报错
- self.num = num
-
- # 重写 父类run方法
- def run(self):
- for i in range(self.num):
- print("正在执行子线程的run方法...",i)
- time.sleep(0.5)
-
- if __name__ == '__main__':
- mythread = MyThread(5)
- mythread.start()
这里创建两个函数,在work1函数中1对全局变量g_num的值进行修改,与此同时在work2函数中获取g_num的值。
- import threading,time
-
- # 定义一个全局变量
- g_num = 0
-
- def work1():
- # 申明g_num是一个全局变量
- global g_num
-
- for i in range(10):
- g_num += 1
- time.sleep(0.5)
- print("work1------%d\n" % g_num)
-
- def work2():
- for i in range(10):
- time.sleep(0.5)
- print("work2------%d\n" % g_num)
-
- if __name__ == '__main__':
- t1 = threading.Thread(target=work1)
- t2 = threading.Thread(target=work2)
-
- t1.start()
- t2.start()
-
- # 在子线程结束后打印g_num
- while len(threading.enumerate()) !=1:
- time.sleep(1)
- print("main------", g_num)
结果:
- import threading,time
-
- # 定义一个全局变量
- g_num = 0
-
- def work1():
- # 申明g_num是一个全局变量
- global g_num
-
- for i in range(1000000):
- g_num += 1
- print("work1------%d\n" % g_num)
-
- def work2():
- global g_num
-
- for i in range(1000000):
- g_num += 1
- print("work2------%d\n" % g_num)
-
- if __name__ == '__main__':
- t1 = threading.Thread(target=work1)
- t2 = threading.Thread(target=work2)
-
- t1.start()
- t2.start()
-
- # 在子线程结束后打印g_num
- while len(threading.enumerate()) !=1:
- time.sleep(1)
- print("main------", g_num)
结果:
解决方法:
1.通过join()方法,让t1线程优先执行,t1执行完毕后,t2才执行
这种方式的缺点是把多线程变成了单线程,影响程序执行效率
- import threading,time
-
- # 定义一个全局变量
- g_num = 0
-
- def work1():
- # 申明g_num是一个全局变量
- global g_num
-
- for i in range(1000000):
- g_num += 1
- print("work1------%d\n" % g_num)
-
- def work2():
- global g_num
-
- for i in range(1000000):
- g_num += 1
- print("work2------%d\n" % g_num)
-
- if __name__ == '__main__':
- t1 = threading.Thread(target=work1)
- t2 = threading.Thread(target=work2)
-
- t1.start()
- t1.join()
- t2.start()
-
- # 在子线程结束后打印g_num
- while len(threading.enumerate()) !=1:
- time.sleep(1)
- print("main------", g_num)
结果:
2.通过加锁解决问题
同步:多个任务之间执行的时候要求有先后顺序,必需一个先执行完成之后,另一个才能继续执行,只有一个主线。如:你说完,我再说(同一时间只能做一件事情)
异步:多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在多条运行主线。如:发微信(可以不用等对方回复继续发)
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其它线程才能再次锁定该资源,互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
使用互斥锁完成两个线程对同一个全局变量各加100万次的操作:
- import threading,time
-
- # 定义一个全局变量
- g_num = 0
-
- def work1():
- # 申明g_num是一个全局变量
- global g_num
-
- lock.acquire() # 加锁
- for i in range(1000000):
- g_num += 1
- lock.release() # 解锁
- print("work1------%d\n" % g_num)
-
- def work2():
- global g_num
-
- lock.acquire()
- for i in range(1000000):
- g_num += 1
- lock.release()
- print("work2------%d\n" % g_num)
-
- if __name__ == '__main__':
- # 创建一把互斥锁
- lock = threading.Lock()
-
- t1 = threading.Thread(target=work1)
- t2 = threading.Thread(target=work2)
-
- t1.start()
- t2.start()
-
- # 在子线程结束后打印g_num
- while len(threading.enumerate()) !=1:
- time.sleep(1)
- print("main------", g_num)
结果:
在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
示例:
- import threading
-
-
- def getValue(index):
- dataList = [1,3,5,7,9]
- lock.acquire()
-
- if index >= len(dataList):
- print("下标越界",index)
- return
- print(dataList[index])
- lock.release()
-
- if __name__ == '__main__':
- # 创建锁
- lock = threading.Lock()
-
- # 创建10个线程
- for i in range(10):
- t = threading.Thread(target=getValue, args=(i,))
- t.start()
结果:(发现当下标越界后程序被阻塞了,原因是return之前没有释放锁)
解决:在return之前释放锁
- import threading
-
-
- def getValue(index):
- dataList = [1,3,5,7,9]
- lock.acquire()
-
- if index >= len(dataList):
- print("下标越界",index)
- lock.release()
- return
- print(dataList[index])
- lock.release()
-
- if __name__ == '__main__':
- # 创建锁
- lock = threading.Lock()
-
- # 创建10个线程
- for i in range(10):
- t = threading.Thread(target=getValue, args=(i,))
- t.start()
结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。