欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python并发复习2 - threading模块

程序员文章站 2022-05-11 14:38:08
一、基本概念 程序: 指令集,静态, 进程: 当程序运行时,会创建进程,是操作系统资源分配的基本单位 线程: 进程的基本执行单元,每个进程至少包含一个线程,是任务调度和执行的基本单位 > 进程和线程之间的关系: ① 一个线程只属于一个进程② 一个进程可以包含多个线程,只有一个主线程 > 进程和线程资 ......

一、基本概念

程序: 指令集,静态,

进程: 当程序运行时,会创建进程,是操作系统资源分配的基本单位

线程: 进程的基本执行单元,每个进程至少包含一个线程,是任务调度和执行的基本单位

> 进程和线程之间的关系:


① 一个线程只属于一个进程
② 一个进程可以包含多个线程,只有一个主线程


>  进程和线程资源对比

① 进程具有独立的空间和系统资源

② 线程不具有独立的空间和系统资源

③ 同一个进程下的多个线程共享该进程的空间和系统资源

④ 局部变量不共享

> 多线程中对于贡献资源修改的问题 
--- 多线程的同步问题、 线程不安全、通过同步解决


二、 多线程

1.需要清楚的一点

  单核cpu: 宏观并行,微观实际上是串行 - 并发
  多核cpu: 微观本质并行

2.应用场合:  

  ① 计算密集型  --- 不适合单核cpu多线程  --- 数值计算
  ② i/o密集型  --- 适合单核cpu多线程   --- 频繁读写

3.优点:

   速度快

4.缺点:

  (1) 线程本身也是程序,线程越多,占用的内存越多;
  (2) 多线程的调用需要协调管理,cpu对线程的跟踪需要消耗内存;
  (3) cpu多线程的切换需要消耗内存
  (4) 多线程之间对共享资源问题,需要解决数据的一致性

三、 线程的创建

三种方式:


(1) threading模块
--- 通过指定target(函数名)和args(函数参数)
(2) 使用thread类,重写run方法
(3) 使用线程池

3.1 threading模块

 1 import threading
 2 import time
 3 def misson(*args):
 4     for i in range(args[1]):
 5         print(i)
 6         time.sleep(1)
 7 # 创建线程对象,参数必须使用元组传递
 8 t = threading.thread(target = mission, args = args)
 9 # 激活线程(排队),等待cpu分配时间片来执行
10 t.start()
11 t.start()

 

3.2. 使用thread类,重写run方法 

        --- 适用于需要创建很多个执行方法相同的线程对象时,用类方法

 1 class my_thread(threading.thread):
 2     def __init__(self, n1):
 3         self.n1 = n1
 4         super().__init__()
 5     # run方法是真正执行函数认为的方法
 6     def run(self):
 7         for i in range(self.end):
 8             print(i)
 9 t1 = my_thread()
10 t1.start(10)

3.3 线程池

线程池的使用threadpool较少,使用concurrent.futures下的 threadpoolexecutor 线程池

 1 from concurrent.futures import threadpoolexecutor
 2 import time
 3 
 4 
 5 def sayhello(a):
 6     print("hello: " + a)
 7     time.sleep(2)
 8 
 9 
10 def main():
11     seed = ["a", "b", "c"]
12 
13     # 第一种方法submit
14     with threadpoolexecutor(3) as executor:
15         for each in seed:
16             executor.submit(sayhello, each)
17 
18     # 第二种方法map
19     with threadpoolexecutor(3) as executor1:
20         executor1.map(sayhello, seed)
21 
22 
23 if __name__ == '__main__':
24     main()

四、 线程的生命周期

(1) 新建 --- 创建线程对象,没有执行能力
(2) 就绪 --- 调用start方法,把执行权利交给cpu
(3) 运行 --- 执行线程任务,获得cpu时间片在一个线程运行时,可能将时间片分配给其他线程
(4) 阻塞 --- 处于等待过程,cpu不给阻塞状态分配时间片
(5) 死亡 --- run方法执行完毕或者抛出没有捕获的异常

五、线程的同步

  --- 在同一个进程下,各个线程共享资源引起不安全,即对成员变量的操作进行共享

1.  抢票问题 - 锁

 1 import time
 2 import threading
 3 
 4 ticket = 100
 5 
 6 def buy_ticket():
 7     global ticket
 8     while ticket:
 9         t = threading.current_thread()
10         print(f'{t.name}{ticket}')
11         time.sleep(0.5)
12         ticket -= 1
13 
14 if __name__ == '__main__':
15     t1 = threading.thread(target=buy_ticket)
16     t1.name = '张三'  # 设定线程名字
17     t1.start()
18     t2 = threading.thread(target=buy_ticket)
19     t2.name = '张四'
20     t2.start()
21     t3 = threading.thread(target=buy_ticket)
22     t3.name = '张五'
23     t3.start()

运行结果如下,会出现重复的抢票,即多个线程获得同一个变量:

Python并发复习2 - threading模块

解决办法:

      使用线程锁, 即在同一时间内,一个共享资源只能被一个线程访问

加锁      --- threading.lock()
抢锁     --- lock.acquare()
解锁     --- lock.release()

 1 import time
 2 import threading
 3 
 4 lock = threading.lock()
 5 ticket = 100
 6 def buy_ticket():
 7     global ticket
 8     while true:
 9         try:
10             lock.acquire()
11             if ticket > 0:
12                 t = threading.current_thread()
13                 time.sleep(0.2)
14                 print(f'{t.name}抢到了第{ticket}张票')
15                 ticket -= 1
16             else:
17                 break
18         finally:
19             lock.release()
20 
21 t1=threading.thread(target=buy_ticket)
22 t1.name="张三"
23 t2=threading.thread(target=buy_ticket)
24 t2.name="李四"
25 t3=threading.thread(target=buy_ticket)
26 t3.name="王五"
27 
28 t1.start()
29 t2.start()
30 t3.start()

 

2. 生产者消费者模型

   (1)  消费者一直消费,商品=0,等待生产                    --- wait 
   (2)  生产者隔一段时间看一次,如果小于3,开始生产 --- 耗费cpu
   (3)  只要消费者消费了产品,通知生产者生产商品       --- notify

   程序见python并发复习2 - threading模块

 

六、多进程

1. 进程创建

(1)使用multiprocessing.process(target=函数名)

(2)继承process重写run


2. 进程操作

os.getpid      # 得到本身进程id

os.getppid    # 得到父进程id

fork:复制进程,只能在linux下使用

其他方法同线程

 

3. 进程队列

进程优于线程:

不存在资源共享问题,没有同步锁,也没有死锁

 

多进程需要处理资源共享问题,使用队列序列化处理(进程队列已经处理好)

 

见生产者消费者程序python并发复习2 - threading模块