当前位置:   article > 正文

python 线程 (概念+示例代码)

python 线程

目录

1. 线程的概念

1.1 主线程

1.2 子线程

1.3 单线程与多线程基本示例代码

2. 线程的数量

3. 线程的参数

4. 守护线程

5. 并行和并发

5.1 多任务的概念

5.2 并发和并行的概念

6. 自定义线程类

7. 多线程共享全局变量

8. 多线程共享全局变量的问题

9. 同步和异步

10. 互斥锁

11. 死锁


1. 线程的概念

        线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属于一个进程的其它线程共享进程所拥有的全部资源。

1.1 主线程

        当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,简而言之:程序启动就会创建一个主线程。

        主线程的重要性体现在两个方面:

        (1)主线程是可以产生其它子线程的线程

        (2)通常它必须最后完成执行,比如执行各种关闭动作

1.2 子线程

        可以看做是程序执行的一条分支,当子线程启动后会和主线程一起同时执行。

1.3 单线程与多线程基本示例代码

 单线程:

  1. import time
  2. def sayHello():
  3. print("Hello")
  4. time.sleep(1)
  5. if __name__ == '__main__':
  6. for i in range(5):
  7. sayHello()

结果:(耗费约5s时间)

多线程:

  1. import time,threading
  2. def sayHello():
  3. print("Hello")
  4. time.sleep(1)
  5. if __name__ == '__main__':
  6. for i in range(5):
  7. # 创建子线程对象
  8. thread_obj = threading.Thread(target=sayHello)
  9. # 启动子线程对象
  10. thread_obj.start()

 结果:(耗费约1s时间)

1.4 主线程会等待所有的子线程结束后才结束

  1. import time,threading
  2. def sing():
  3. for i in range(3):
  4. print("is singing... %d" % i)
  5. time.sleep(1)
  6. def dance():
  7. for i in range(3):
  8. print("is dancing... %d" % i)
  9. time.sleep(1)
  10. if __name__ == '__main__':
  11. print("The main thread starts to execute")
  12. t1 = threading.Thread(target=sing)
  13. t2 = threading.Thread(target=dance)
  14. t1.start()
  15. t2.start()
  16. print("Main thread execution completed")

结果:(可以看到主线程执行完毕后,在等待所有线程执行完毕后才结束运行)

2. 线程的数量

        使用threading.enumerate()可以获取当前所有活跃的线程对象列表,再使用len()查看活跃的线程数量。

  1. import time,threading
  2. def sing():
  3. for i in range(3):
  4. print("is singing... %d" % i)
  5. time.sleep(1)
  6. def dance():
  7. for i in range(3):
  8. print("is dancing... %d" % i)
  9. time.sleep(1)
  10. if __name__ == '__main__':
  11. thread_list = threading.enumerate()
  12. print("\nCurrent number of threads:%d" % len(thread_list))
  13. t1 = threading.Thread(target=sing)
  14. t2 = threading.Thread(target=dance)
  15. t1.start()
  16. t2.start()
  17. thread_list = threading.enumerate()
  18. print("\nCurrent number of threads:%d" % len(thread_list))

结果:

3.线程的参数

线程传递参数有三种方法:

        1.使用元组传递:

        threading.Thread(target=xxx, args=(参数1,参数2,....))

  1. import time,threading
  2. def test(a,b,c):
  3. print("a=%d,b=%d,c=%d" % (a,b,c))
  4. time.sleep(1)
  5. if __name__ == '__main__':
  6. for i in range(5):
  7. # 创建子线程对象
  8. thread_obj = threading.Thread(target=test, args=(10,20,30))
  9. # 启动子线程对象
  10. thread_obj.start()

结果:

 

        2.使用字典传递

        threading.Thread(target=xxx, kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})

  1. import time,threading
  2. def test(a,b,c):
  3. print("a=%d,b=%d,c=%d" % (a,b,c))
  4. time.sleep(1)
  5. if __name__ == '__main__':
  6. for i in range(5):
  7. # 创建子线程对象
  8. thread_obj = threading.Thread(target=test, kwargs={"a":10,"c":20,"b":30,})
  9. # 启动子线程对象
  10. thread_obj.start()

结果:

        3.同时使用元组和字典

        threading.Thread(target=xxx, args=(参数1,参数2,....), kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})

  1. import time,threading
  2. def test(a,b,c):
  3. print("a=%d,b=%d,c=%d" % (a,b,c))
  4. time.sleep(1)
  5. if __name__ == '__main__':
  6. for i in range(5):
  7. # 创建子线程对象
  8. thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
  9. # 启动子线程对象
  10. thread_obj.start()

结果:

4.守护线程

        如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。

  1. import time,threading
  2. def test(a,b,c):
  3. for i in range(5):
  4. print("正在输出:%d" % (i))
  5. time.sleep(1)
  6. if __name__ == '__main__':
  7. # 创建子线程对象
  8. thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
  9. # 设置线程守护:子线程守护主线程
  10. thread_obj.setDaemon(True)
  11. # 启动子线程对象
  12. thread_obj.start()
  13. time.sleep(1)
  14. print("主线程即将结束...")
  15. # 退出主线程
  16. exit()

结果:

5. 并行和并发

5.1 多任务的概念

        其实就是操作系统轮流让各个任务交替执行,如:任务1执行0.01秒,然后任务2执行0.01秒,再让任务3执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是由于CPU执行速度非常快,我们就感觉所有任务都在同时执行一样。

5.2 并发和并行的概念

        并发:并发指的是任务数大于CPU核数,通过操作系统的各种调度算法,实现用多个任务“一起”执行(实际总有一些任务不执行,因为切换任务的速度相当快,看上去一起执行而已)

        并行:指的是任务数小于或等于CPU核数,即任务真的是一起执行的。

        真正的并行执行多任务只能在多核CPU上实现,但是,由于人物数量远远多于CPU的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行。

6. 自定义线程类

        通过threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要以下三步:

        1.让自定义类继承 threading.Thread

        2.让自定义类重写run方法

        3.通过实例化自定义类对象.start()方法启动自定义线程

  1. import threading,time
  2. # 自定义线程类
  3. class MyThread(threading.Thread):
  4. def __init__(self, num):
  5. super().__init__() # 要先调用父类的init方法否则会报错
  6. self.num = num
  7. # 重写 父类run方法
  8. def run(self):
  9. for i in range(self.num):
  10. print("正在执行子线程的run方法...",i)
  11. time.sleep(0.5)
  12. if __name__ == '__main__':
  13. mythread = MyThread(5)
  14. mythread.start()

7. 多线程共享全局变量

        这里创建两个函数,在work1函数中1对全局变量g_num的值进行修改,与此同时在work2函数中获取g_num的值。

  1. import threading,time
  2. # 定义一个全局变量
  3. g_num = 0
  4. def work1():
  5. # 申明g_num是一个全局变量
  6. global g_num
  7. for i in range(10):
  8. g_num += 1
  9. time.sleep(0.5)
  10. print("work1------%d\n" % g_num)
  11. def work2():
  12. for i in range(10):
  13. time.sleep(0.5)
  14. print("work2------%d\n" % g_num)
  15. if __name__ == '__main__':
  16. t1 = threading.Thread(target=work1)
  17. t2 = threading.Thread(target=work2)
  18. t1.start()
  19. t2.start()
  20. # 在子线程结束后打印g_num
  21. while len(threading.enumerate()) !=1:
  22. time.sleep(1)
  23. print("main------", g_num)

结果:

8. 多线程共享全局变量的问题

  1. import threading,time
  2. # 定义一个全局变量
  3. g_num = 0
  4. def work1():
  5. # 申明g_num是一个全局变量
  6. global g_num
  7. for i in range(1000000):
  8. g_num += 1
  9. print("work1------%d\n" % g_num)
  10. def work2():
  11. global g_num
  12. for i in range(1000000):
  13. g_num += 1
  14. print("work2------%d\n" % g_num)
  15. if __name__ == '__main__':
  16. t1 = threading.Thread(target=work1)
  17. t2 = threading.Thread(target=work2)
  18. t1.start()
  19. t2.start()
  20. # 在子线程结束后打印g_num
  21. while len(threading.enumerate()) !=1:
  22. time.sleep(1)
  23. print("main------", g_num)

结果:

 解决方法:

        1.通过join()方法,让t1线程优先执行,t1执行完毕后,t2才执行

        这种方式的缺点是把多线程变成了单线程,影响程序执行效率

  1. import threading,time
  2. # 定义一个全局变量
  3. g_num = 0
  4. def work1():
  5. # 申明g_num是一个全局变量
  6. global g_num
  7. for i in range(1000000):
  8. g_num += 1
  9. print("work1------%d\n" % g_num)
  10. def work2():
  11. global g_num
  12. for i in range(1000000):
  13. g_num += 1
  14. print("work2------%d\n" % g_num)
  15. if __name__ == '__main__':
  16. t1 = threading.Thread(target=work1)
  17. t2 = threading.Thread(target=work2)
  18. t1.start()
  19. t1.join()
  20. t2.start()
  21. # 在子线程结束后打印g_num
  22. while len(threading.enumerate()) !=1:
  23. time.sleep(1)
  24. print("main------", g_num)

结果:

         2.通过加锁解决问题

9. 同步和异步

        同步:多个任务之间执行的时候要求有先后顺序,必需一个先执行完成之后,另一个才能继续执行,只有一个主线。如:你说完,我再说(同一时间只能做一件事情)

        异步:多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在多条运行主线。如:发微信(可以不用等对方回复继续发)

10. 互斥锁

         当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

        互斥锁为资源引入一个状态:锁定/非锁定

        某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其它线程才能再次锁定该资源,互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

使用互斥锁完成两个线程对同一个全局变量各加100万次的操作:

  1. import threading,time
  2. # 定义一个全局变量
  3. g_num = 0
  4. def work1():
  5. # 申明g_num是一个全局变量
  6. global g_num
  7. lock.acquire() # 加锁
  8. for i in range(1000000):
  9. g_num += 1
  10. lock.release() # 解锁
  11. print("work1------%d\n" % g_num)
  12. def work2():
  13. global g_num
  14. lock.acquire()
  15. for i in range(1000000):
  16. g_num += 1
  17. lock.release()
  18. print("work2------%d\n" % g_num)
  19. if __name__ == '__main__':
  20. # 创建一把互斥锁
  21. lock = threading.Lock()
  22. t1 = threading.Thread(target=work1)
  23. t2 = threading.Thread(target=work2)
  24. t1.start()
  25. t2.start()
  26. # 在子线程结束后打印g_num
  27. while len(threading.enumerate()) !=1:
  28. time.sleep(1)
  29. print("main------", g_num)

结果:

11. 死锁

        在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

示例:

  1. import threading
  2. def getValue(index):
  3. dataList = [1,3,5,7,9]
  4. lock.acquire()
  5. if index >= len(dataList):
  6. print("下标越界",index)
  7. return
  8. print(dataList[index])
  9. lock.release()
  10. if __name__ == '__main__':
  11. # 创建锁
  12. lock = threading.Lock()
  13. # 创建10个线程
  14. for i in range(10):
  15. t = threading.Thread(target=getValue, args=(i,))
  16. t.start()

结果:(发现当下标越界后程序被阻塞了,原因是return之前没有释放锁)

 解决:在return之前释放锁

  1. import threading
  2. def getValue(index):
  3. dataList = [1,3,5,7,9]
  4. lock.acquire()
  5. if index >= len(dataList):
  6. print("下标越界",index)
  7. lock.release()
  8. return
  9. print(dataList[index])
  10. lock.release()
  11. if __name__ == '__main__':
  12. # 创建锁
  13. lock = threading.Lock()
  14. # 创建10个线程
  15. for i in range(10):
  16. t = threading.Thread(target=getValue, args=(i,))
  17. t.start()

结果:

了解python 进程请参考:

python 进程 (概念+示例代码)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/74822
推荐阅读
相关标签
  

闽ICP备14008679号