Python之进程、线程、锁


一、什么是线程。什么是进程。

一个应用程序:软件
一个应用程序:可以创建多个进程(默认一个进程),一个进程可以创建多个线程(默认一个线程)

线程:工作的最小单元,共享进程中的所有资源,每个线程分担一点任务,最终完成最后的结果
进程:独立开辟内存 进程之间的数据隔离,最小资源单位
总结: 1.操作系统帮助开发者操作硬件 
       2.程序员写好代码在操作系统上运行

任务特别多:
    3.串行 一个个的去执行
      1 写好代码
      2 交给解释器运行
      3 解释器读取代码,再交给操作系统执行,根据写的代码选择创建线程/进程去执行(单进程/单线程)

    4.多线程的话
      1 写好代码
      2 交给解释器运行
      3 解释器读取代码,再交给操作系统执行,根据写的代码选择创建线程/进程去执行(单进程/多线程)

 

       GIL:Python内置的全局解释器锁:用于限制一个进程中同一时刻只有一个线程被cpu调度
         线程之间切换默认gil锁在执行100个cpu指令的时候就切换 还有一个就是过期时间

          为什么还保留这把锁?对于语言的创始人来说:在开发这门语言时候,目的是最开始为了方便快速的把这个语言开发出来,假如没有这把锁处理的事情非常多,比如一个进程里边有三个线程同时被CPU调度,那么这三个线程同时修改一个值,这里就要处理,还有就是线程执行到一半要终端,那么就要记住本次线程操作的状态下次回来执行这个线程的时候继续从这里开始执行。

          线程创建的越多越好吗?不是的  线程之间进行切换,要进行上下文管理CPU分片处理要记录线程运行状态

      

1 import sys
2 
3 v = sys.getcheckinterval()
4 print(v)

 

    IO操作不占用CPU
Python多线程情况下:
    计算密集型操作:效率低(GIL锁)
    IO密集型操作:效率高

Python多进程情况下:
    计算密集型操作:效率高(浪费资源)不得已而为之
    IO密集型操作:效率高 (浪费资源)

Java多线程情况下:
    计算密集型操作:效率高
    IO密集型操作:效率高

Java多进程程情况下:(Java程序员一般不写多进程)
    计算密集型操作:效率高
    IO密集型操作:效率高

自己画了图,图解非常清晰很容易理解(对比Java)

实例一:主线程默认等子线程执行完毕

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import threading
 5 import time
 6 
 7 def func(arg):
 8     time.sleep(arg)
 9     print(arg)
10 
11 
12 t1 = threading.Thread(target=func, args=(3,))
13 t1.start()
14 
15 t2 = threading.Thread(target=func, args=(9,))
16 t2.start()
17 
18 print('123')

 

执行结果:

123
3
9

 

实例二、主线程不再等子线程,主线程终止则所有子线程终止

 1 # def func(arg):
 2 #     time.sleep(arg)
 3 #     print(arg)
 4 #
 5 #
 6 # t1 = threading.Thread(target=func, args=(3,))
 7 # t1.setDaemon(True)  # 不等子线程
 8 # t1.start()
 9 #
10 # t2 = threading.Thread(target=func, args=(9,))
11 # t2.setDaemon(True)
12 # t2.start()
13 #
14 # print(123)
15 
16 # 123

 

实例三、开发者可以控制主线程等待子线程(最多等待时间)

 1 # def func(arg):
 2 #     time.sleep(10)
 3 #     print(arg)
 4 #
 5 #
 6 # print('创建子线程t1')
 7 # t1 = threading.Thread(target=func, args=(3,))
 8 # t1.start()
 9 # t1.join()  # 让主线程在这里等着,等到子线程t1执行完毕继续往下走
10 # # t1.join(1)  # 主线程最多等1s
11 # print('创建子线程t2')
12 # t2 = threading.Thread(target=func, args=(9,))
13 # t2.start()
14 # t2.join()  # 让主线程在这里等着,等到子线程t2执行完毕继续往下走  这样搞纯粹没意义了开线程没卵用就是串行了
15 # # t2.join(1) # 主线程最多等1s
16 # print(123)
17 # 创建子线程t1
18 # 3
19 # 创建子线程t2
20 # 9
21 # 123

 

实例四、获取线程名称

 1 # def func(arg):
 2 #     # 获取当前执行该函数的线程的名称
 3 #     t = threading.current_thread()
 4 #     name = t.getName()
 5 #     print(name, arg)
 6 #
 7 #
 8 # t1 = threading.Thread(target=func, args=(3,))
 9 # t1.setName('t111111')
10 # t1.start()
11 #
12 #
13 # t2 = threading.Thread(target=func, args=(9,))
14 # t2.setName('t222222')
15 # t2.start()
16 #
17 # print(123)

 

实例五、线程本质

 1 # 先打印 3?还是123?  这个不确定  要看cpu现在忙不忙,有没有空余时间
 2 # def func(arg):
 3 #     print(arg)
 4 #
 5 #
 6 # t1 = threading.Thread(target=func, args=(3,))
 7 # t1.start()  # 是开始运行线程吗?  不是
 8 # # start告诉cpu 我已经准备好了,你可以调度我了
 9 #
10 #
11 # print(123)

 

实例六、面向对象版本的线程

自己写run方法让自己的线程去执行自己想执行的任务,就不走Thread里边内置的run方法了

 1 class MyThread(threading.Thread):
 2     # 看源码发现的
 3     """
 4     self._target = target
 5     if self._target:
 6         self._target(*self._args, **self._kwargs)
 7     """
 8     def run(self):
 9         print('11111', self._args, self._kwargs)
10 
11 
12 # def func(arg):
13 #     print(arg)
14 
15 
16 # t1 = MyThread(target=func, args=(11,))
17 t1 = MyThread(args=(11,), kwargs={'name': 1})
18 t1.start()
19 # 11111 (11,) {'name': 1}

 实例七:多线程的问题 

    如果现在要对全局的一个变量值进行修改,那么线程拿到的数据有可能不安全的

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 lock = threading.Lock()
 8 n = 5
 9 
10 
11 def func(i):
12     print('这段代码不加锁', i)
13 
14     lock.acquire()  # 加锁  此区域的代码同一时刻只能一个线程执行
15     global n
16     print('当前线程', i, '读取到n的值', n)
17     n = i
18     time.sleep(1)
19     print('当前线程', i, '修改n的值', n)
20     lock.release()  # 释放锁
21 
22 
23 for i in range(5):
24     t = threading.Thread(target=func, args=(i,))
25     t.start()

 

执行结果:

 1 这段代码不加锁 0
 2 当前线程 0 读取到n的值 5
 3 这段代码不加锁 1
 4 这段代码不加锁 2
 5 这段代码不加锁 3
 6 这段代码不加锁 4
 7 当前线程 0 修改n的值 0
 8 当前线程 1 读取到n的值 0
 9 当前线程 1 修改n的值 1
10 当前线程 2 读取到n的值 1
11 当前线程 2 修改n的值 2
12 当前线程 3 读取到n的值 2
13 当前线程 3 修改n的值 3
14 当前线程 4 读取到n的值 3
15 当前线程 4 修改n的值 4
16 
17 这段代码不加锁 0
18 当前线程 0 读取到n的值 5
19 这段代码不加锁 1
20 当前线程 1 读取到n的值 0
21 这段代码不加锁 2
22 当前线程 2 读取到n的值 1
23 这段代码不加锁 3
24 当前线程 3 读取到n的值 2
25 这段代码不加锁 4
26 当前线程 4 读取到n的值 3
27 当前线程 2 修改n的值 4
28 当前线程 1 修改n的值 4
29 当前线程 0 修改n的值 4
30 当前线程 4 修改n的值 4
31 当前线程 3 修改n的值 4
奇葩结果

 

二、锁

为什么要用锁?

解决线程不安全+程序员可控制一段代码

线程安全:多线程操作时,内部会让所有线程排队处理 如:列表 字典 Queue

线程不安全:不安全就要上锁让所有线程进行排队处理

实例一:线程安全实例  

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import threading
 5 
 6 v = []
 7 
 8 
 9 def task(arg):
10     v.append(arg)  # 线程安全 要放就要等它放完
11     print(v)
12 
13 
14 for i in range(10):
15     t = threading.Thread(target=task, args=(i,))
16     t.start()

 Rlock与Lock的比较

RLock 一次放行一个,支持锁多次解多次

lock 一次放行一个线程,锁一次 解一次 不能同时加几把锁 不然一个线程都进不来(死锁) 基本不用

 实例二:线程不安全  需求往列表中添加线程号 然后再拿出最后一个线程号

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 # lock = threading.Lock()  
 7 lock = threading.RLock()  
 8 
 9 
10 def task(arg):
11     lock.acquire()
12     lock.acquire()
13     v.append(arg)
14     time.sleep(0.01)
15     m = v[-1]
16     print(arg, m)
17     lock.release()
18     lock.release()
19 
20 
21 for i in range(10):
22     t = threading.Thread(target=task, args=(i,))
23     t.start()

 

实例三、BoundedSemaphore信号量 定死了放行的几个

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 # 锁:BoundedSemaphore信号量 定死了放行的几个
 8 lock = threading.BoundedSemaphore(3)
 9 """
10     def __init__(self, value=1):# 默认一个 
11         Semaphore.__init__(self, value)
12         self._initial_value = value
13 """
14 
15 
16 def task(arg):
17     lock.acquire()  # 一次放行三个
18     print(arg)
19     time.sleep(1)
20     lock.release()
21 
22 
23 for i in range(15):
24     t = threading.Thread(target=task, args=(i,))
25     t.start()

 

 实例四、根据输入放行线程个数来放行线程Condition

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import time
import threading

lock = threading.Condition()  
def task(arg):
    print('线程进来了')
    lock.acquire()   
    print(111111)
    lock.wait()  # 加锁
    print(arg)
    time.sleep(1)
    lock.release()


for i in range(15):
    t = threading.Thread(target=task, args=(i,))
    t.start()

while True:
    inp = int(input(">>>"))
    lock.acquire()
    lock.notify(inp)
    lock.release()

 

另外一种方法:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 lock = threading.Condition()  # 根据输入放线程通过
 8 
 9 
10 def xxx():
11     print('来执行啦')
12     input('>>>')
13     return True
14 
15 
16 def task(arg):
17     print('线程进来了')
18     lock.wait_for(xxx)  # 满足条件就往下走
19     print(arg)
20     time.sleep(1)
21 
22 
23 for i in range(15):
24     t = threading.Thread(target=task, args=(i,))
25     t.start()

 

 实例五、Event一次放所有 理解成红绿灯就行了

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import threading
 5 
 6 lock = threading.Event()
 7 
 8 
 9 def task(arg):
10     print('线程来了')
11     lock.wait()  # 加锁  红灯
12     print(arg)
13 
14 
15 for i in range(5):
16     t = threading.Thread(target=task, args=(i,))
17     t.start()
18 
19 input(">>>")
20 lock.set()  # 绿灯
21 lock.clear()  # 再次变红灯
22 for i in range(5):
23     t = threading.Thread(target=task, args=(i,))
24     t.start()
25 lock.set()  # 绿灯

 

实例六、给当前线程创建空间用于存储值threadinglocal

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 
 7 v = threading.local()
 8 
 9 
10 def func(arg):
11     # 内部会为当前线程创建一个空间用于存储值
12     v.phone = arg
13     time.sleep(2)
14     # 然后就可以在当前独立的空间取值
15     print(v.phone, arg)
16 
17 
18 for i in range(5):
19     t = threading.Thread(target=func, args=(i,))
20     t.start()

 

 三、线程池   

  一次最多放行几个线程,超过线程池的个数,会下一批在放行

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 from concurrent.futures import ThreadPoolExecutor
 5 import time
 6 
 7 
 8 def task(a1, a2):
 9     time.sleep(2)
10     print(a1, a2)
11 
12 
13 pool = ThreadPoolExecutor(3)  # 一次最多放行3个 如果超过3个就得分次数了 12/3
14 
15 for i in range(12):
16     pool.submit(task, i, 2)

 

四、生产者消费者  使用多线程+队列来实现模型

解决了消费者不用在那里等生产者生产东西,两个对象互不影响(比如:买车票,假如有一个管道,我们生产者就把信息放到管道中去,由车票售员去管道拿信息消费任务)

队列 先进先出
栈 后进先出(上子弹夹)
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 import threading
 6 import queue
 7 
 8 q = queue.Queue()
 9 
10 
11 def producer(arg):
12     """
13     生产者
14     :param arg:
15     :return:
16     """
17     while True:
18         time.sleep(1)
19         print("厨师生产包子%s号" % arg)
20         q.put(arg)
21 
22 
23 for i in range(1, 4):
24     t = threading.Thread(target=producer, args=(i,))
25     t.start()
26 
27 
28 def consumer(arg):
29     """
30     消费者
31     :param arg:
32     :return:
33     """
34     while True:
35         time.sleep(1)
36         q.get(arg)
37         print("消费者吃包子")
38 
39 
40 for i in range(1, 3):
41     t = threading.Thread(target=consumer, args=(i,))
42     t.start()

 五、进程

 注意:在windows下和linux下码代码不一样   在windows下执行要放在main下执行

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 
 7 def task(arg):
 8     print(arg)
 9 
10 
11 if __name__ == '__main__':
12     for i in range(10):
13         p = multiprocessing.Process(target=task, args=(i,))
14         p.start()

 

实例一、进程之间数据不共享

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 data_list = []
 7 
 8 
 9 def task(arg):
10     data_list.append(arg)
11     print(data_list)
12 
13 
14 if __name__ == '__main__':
15     for i in range(10):
16         p = multiprocessing.Process(target=task, args=(i,))
17         p.start()
18 """
19 [0][1][2][3][4][5][6][7][8][9]
20 """

 

实例 进程常规操作

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import time
import multiprocessing


def task(arg):
    p = multiprocessing.current_process()
    print(p.name, p.ident, p.pid)
    time.sleep(2)
    print(arg)


def run():
    print('111')
    p1 = multiprocessing.Process(target=task, args=(1,))
    # p1.daemon = True  # 主进程执行完毕直接不等子进程了默认False
    p1.start()
    p1.name = 'pp1'
    # p1.join(9)  # 等待进程完毕  最多9秒
    print('222')

    p2 = multiprocessing.Process(target=task, args=(2,))
    # p2.daemon = True
    p2.start()
    p2.name = 'pp2'
    # p2.join()
    print('333')


if __name__ == '__main__':
    run()

 

实例二、进程间的数据共享方式一multiprocessing.Queue()

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 
 7 def task(arg, q):
 8     q.put(arg)
 9 
10 
11 if __name__ == '__main__':
12     q = multiprocessing.Queue()  # 进程间数据共享
13     for i in range(10):
14         p = multiprocessing.Process(target=task, args=(i, q))
15         p.start()
16 
17     while True:
18         v = q.get()
19         print(v)

 

 实例三、进程中数据共享的方式二multiprocessing.Manager()

 

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import multiprocessing
 5 
 6 
 7 def task(arg, dic):
 8     dic[arg] = 100
 9 
10 
11 if __name__ == '__main__':
12     m = multiprocessing.Manager()
13     dic = m.dict()
14     for i in range(10):
15         p = multiprocessing.Process(target=task, args=(i, dic,))
16         p.start()
17         p.join()
18 
19     # v = dic.items()
20     # print(v)
21     print(dic)

 

实例四、进程锁 和线程差不多

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 
4 import multiprocessing
5 
6 lock = multiprocessing.RLock()
7 # 进程锁跟线程锁差不多  就是进程之间用同一数据的时候就加锁

 

实例五、进程池

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from concurrent.futures import ProcessPoolExecutor

pool = ProcessPoolExecutor(5)

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM