线程

线程是什么?

进程是资源分配的最小单位,线程是CPU调度的最小单位.每个进程中至少有一个线程。

为什么有了进程还需要线程?

60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。(官方说法)
进程的缺点:
  • 进程在执行任务中如果堵塞,则整个进程就会被挂起
  • 进程在同一时间只能执行一个任务(开子进程也只是为了解决一个任务)
  • 进程之间的数据是隔离的。想要取到不同进程之间的数据很难
  • 多个进程并行开销过大(时间长)

了解了进程的缺点了之后,线程的概念就出来了。线程正好就是解决这些问题的。

通过代码来解释线程的功能以及特性

1、线程的创建
from threading import Threaddef func(n):print("线程的创建", n)t = Thread(target=func, args=('pontoon',))
t.start()

# 多线程并发
import os
import time
from threading import Thread
from multiprocessing import Processdef func():time.sleep(2)print('hello', os.getpid())if __name__ == '__main__':# for i in range(10):#     p1 = Process(target=func)#     p1.start()#     print('主进程pid', os.getpid())#     print('-' * 30)for i in range(10):t1 = Thread(target=func)t1.start()print('主线程pid', os.getpid())# 结果我就不贴出来了
# 两者并没有什么实质上的区别,当然也看的出一点点差异就是主线程执行(全部一起出结果)要比主进程快(5个5个来)

2、线程与进程的对比
1、pid的对比

 1 import os
 2 from threading import Thread
 3 from multiprocessing import Process
 4
 5
 6 def func():
 7     print('hello', os.getpid())
 8
 9
10 if __name__ == '__main__':
11     p1 = Process(target=func)
12     p2 = Process(target=func)
13     p1.start()
14     p2.start()
15     print('主进程pid', os.getpid())
16     print('-' * 30)
17
18     t1 = Thread(target=func)
19     t2 = Thread(target=func)
20     t1.start()
21     t2.start()
22     print('主线程pid', os.getpid())
23
24 # 注意执行顺序  程序从上往下执行,最后执行子进程
25 >>>主进程pid 155928
26 ------------------------------
27 hello 155928        # 子线程
28 hello 155928        # 子线程
29 主线程pid 155928
30 hello 134120        # 子进程
31 hello 74396         # 子进程

View Code

  • 从上述代码的执行结果来看所有线程的pid都指向一个155928即主进程的pid,可以的出结论:一个进程可以包含至少一个线程。
  • 从代码的执行顺序来看。主进程会随着子进程代码的执行完毕才结束,但是主线程与子线程则是从上向下按顺序执行。
2、开启效率的对比:

 1 # 开启效率的对比:
 2 import os
 3 import time
 4 from threading import Thread
 5 from multiprocessing import Process
 6
 7
 8 def func():
 9     time.sleep(2)
10     print('hello', os.getpid())
11
12
13 if __name__ == '__main__':
14     # start_time = time.time()
15     # for i in range(30):
16     #     p1 = Process(target=func)
17     #     p1.start()
18     #     print('主进程pid', os.getpid())
19     # end_time = time.time()
20     # print('并发进程所用时间:',end_time - start_time)
21
22     start_time = time.time()
23     for i in range(30):
24         t1 = Thread(target=func)
25         t1.start()
26         print('主线程pid', os.getpid())
27     end_time = time.time()
28     print('并发线程所用时间:', end_time - start_time)
29
30 >>>并发线程所用时间: 0.006010770797729492
31     并发进程所用时间: 1.3949854373931885
32 # 差距相当大

View Code

3、内存数据的共享

 1 # 3、线程之间数据的共享问题
 2
 3 import os
 4 from threading import Thread
 5
 6
 7 def func():
 8     global g
 9     g = 44
10     print(g, os.getpid())
11
12
13 g = 100
14 t_list = []
15 for i in range(3):
16     t = Thread(target=func)
17     t.start()
18     t_list.append(t)
19
20 for i in t_list: t.join()
21 print(g)
22
23 >>>44 112740
24 44 112740
25 44 112740
26 44

View Code

4、多线程中的input

 1 import os
 2 from threading import Thread
 3
 4
 5 def func(n):
 6     inp = input('%s:' % n)
 7     print(inp)
 8
 9
10 for i in range(3):
11     t = Thread(target=func, args=('name%s' % i, ))
12     t.start()
13
14 >>>name0:name1:name2:fgh
15 fgh
16 # 有点懵逼

View Code

总结
进程是最小的内存分配单位
线程是操作系统调度的最小单位进程中至少含有一个线程
进程中可以开启多个线程开启线程的时间要远小于进程
多个线程内部都有自己的数据栈,数据栈中的数据不共享
全局变量在多线程之间是共享的
多线程是可以使用Input的(IO请求并不会堵塞)

守护线程 / 守护进程

必问的一个面试题   GIL(全局解释器锁)

在cpython解释器中为了防止线程竞争,导致数据不安全会在进程中加上GIL锁,用来保证在同一个进程在同一时刻,只有一个线程被CPU调度。

线程锁

一段代码引出线程锁的概念
from threading import Thread, Lock
import timedef func():global ntemp = ntime.sleep(1)n = temp - 1n = 10        # 线程的数据是共享的
t_list = []
for i in range(10):t = Thread(target=func)t.start()t_list.append(t)for i in t_list:i.join()print(n)>>>9        # 得到的为什么会是一个9

画图进行解释:
这个时候你还会问,不是有GIL吗?线程被锁住了不应该一个一个的去取数据吗?
用到了GIL之后数据仍然不安全。
操作系统采用时间片轮转算法来执行线程(假设是0.01s),一次线程的执行需1s才能执行完成。可想而知,如果只是单纯的加上GIL数据仍然是不安全的。
线程锁能确保一个线程执行完一次任务,所以在加上锁就能解决这个问题!

 1 def func(lock):
 2     lock.acquire()
 3     global n
 4     temp = n
 5     time.sleep(1)
 6     n = temp - 1
 7     lock.release()
 8
 9
10 n = 10
11 lock = Lock()
12 t_list = []
13 for i in range(10):
14     t = Thread(target=func, args=(lock, ))
15     t.start()
16     t_list.append(t)
17
18 for i in t_list:
19     i.join()
20
21 >>>0    

View Code

这里还会出现一个问题就是科学家吃面的问题——死锁(互斥锁)

 1 import time
 2 from threading import Thread, Lock
 3
 4 noodle_lock = Lock()
 5 fork_lock = Lock()
 6
 7
 8 def eat1(name):
 9     noodle_lock.acquire()
10     print('%s 抢到了面条'%name)
11     fork_lock.acquire()
12     print('%s 抢到了叉子'%name)
13     print('%s 吃面'%name)
14     fork_lock.release()
15     noodle_lock.release()
16
17
18 def eat2(name):
19     fork_lock.acquire()
20     print('%s 抢到了叉子' % name)
21     time.sleep(1)
22     noodle_lock.acquire()
23     print('%s 抢到了面条' % name)
24     print('%s 吃面' % name)
25     noodle_lock.release()
26     fork_lock.release()
27
28
29 for name in ['aaa', 'bbb', 'ccc']:
30     t1 = Thread(target=eat1, args=(name,))
31     t2 = Thread(target=eat2, args=(name,))
32     t1.start()
33     t2.start()
34
35 >>>aaa 抢到了面条
36 aaa 抢到了叉子
37 aaa 吃面
38 aaa 抢到了叉子
39 bbb 抢到了面条    

View Code

什么叫互斥锁
是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。上述的代码就是死锁现象。
大白话来讲就是多个线程、进程遇见多个锁,就会产生死锁现象。
解决死锁,递归锁
什么叫做递归锁?
下图基本上就解释了递归锁的概念

在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock。

用递归锁解决死锁代码

 1 import time
 2 from threading import Thread, Rock
 3
 4
 5 fork_lock = noodle_lock = RLock()        # 注意这种写法
 6 def eat1(name):
 7     noodle_lock.acquire()
 8     print('%s 抢到了面条'%name)
 9     fork_lock.acquire()
10     print('%s 抢到了叉子'%name)
11     print('%s 吃面'%name)
12     fork_lock.release()
13     noodle_lock.release()
14
15
16 def eat2(name):
17     fork_lock.acquire()
18     print('%s 抢到了叉子' % name)
19     time.sleep(1)
20     noodle_lock.acquire()
21     print('%s 抢到了面条' % name)
22     print('%s 吃面' % name)
23     noodle_lock.release()
24     fork_lock.release()
25
26
27 for name in ['aaa', 'bbb', 'ccc']:
28     t1 = Thread(target=eat1, args=(name,))
29     t2 = Thread(target=eat2, args=(name,))
30     t1.start()
31     t2.start()
32
33 aaa 抢到了面条
34 aaa 抢到了叉子
35 aaa 吃面
36 aaa 抢到了叉子
37 aaa 抢到了面条
38 aaa 吃面
39 bbb 抢到了面条
40 bbb 抢到了叉子
41 bbb 吃面
42 bbb 抢到了叉子
43 bbb 抢到了面条
44 bbb 吃面
45 ccc 抢到了面条
46 ccc 抢到了叉子
47 ccc 吃面
48 ccc 抢到了叉子
49 ccc 抢到了面条
50 ccc 吃面    

View Code

信号量

 1 # 7、信号量
 2
 3 from threading import Thread,Semaphore
 4 import threading
 5 import time
 6
 7
 8 def func():
 9     sm.acquire()
10     print('%s get sm' %threading.current_thread().getName())
11     time.sleep(3)
12     sm.release()
13
14
15 if __name__ == '__main__':
16     sm=Semaphore(5)
17     for i in range(23):
18         t=Thread(target=func)
19         t.start()

View Code

同进程:规定锁的个数

事件

同进程

 1 import threading
 2 import time,random
 3 from threading import Thread,Event
 4
 5 def conn_mysql():
 6     count=1
 7     while not event.is_set():
 8         if count > 3:
 9             raise TimeoutError('链接超时')
10         print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count))
11         event.wait(0.5)
12         count+=1
13     print('<%s>链接成功' %threading.current_thread().getName())
14
15
16 def check_mysql():
17     print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName())
18     time.sleep(random.randint(2,4))
19     event.set()
20 if __name__ == '__main__':
21     event=Event()
22     conn1=Thread(target=conn_mysql)
23     conn2=Thread(target=conn_mysql)
24     check=Thread(target=check_mysql)
25
26     conn1.start()
27     conn2.start()
28     check.start()

View Code

条件

 1 from threading import Condition, Thread
 2
 3
 4 def func(con, i):
 5     con.acquire()
 6     con.wait()
 7     print('在第{0}个循环里'.format(i))
 8     con.release()
 9
10
11 con = Condition()
12 for i in range(10):
13     Thread(target=func, args=(con, i)).start()
14 while True:
15     num = int(input('>>>'))
16     con.acquire()
17     con.notify(num)     # 在钥匙
18     con.release()

View Code

线程队列

为什么线程能够进行数据共享还要在用队列?
因为数据安全的问题。队列中内置了锁。保证数据安全!
就那几个方法,没啥好说的详见女神博客

线程池 (concurrent.futures)

 1 import time
 2 from concurrent.futures import ThreadPoolExecutor
 3
 4
 5 def func(n):
 6     time.sleep(2)
 7     print(n)
 8     return n * n
 9
10
11 t_pool = ThreadPoolExecutor(max_workers=5)      # 在线程池中创建5个线程
12 t_list = []
13 for i in range(20):
14     t = t_pool.submit(func, i)      # 创建子线程
15     t_list.append(t)
16
17 t_pool.shutdown()           # close() + join() 方法
18 print('主线程')
19 for i in t_list:
20     print("***", t.result())    # 取return的结果

View Code

线程池中增加回调函数

 1 # 11、线程池中加回调函数
 2 import time
 3 from concurrent.futures import ThreadPoolExecutor
 4
 5
 6 def func(n):
 7     time.sleep(2)
 8     print(n)
 9     return n * n
10
11
12 def call_back(m):
13     print("结果是 %s" % m.result())
14
15
16 t_pool = ThreadPoolExecutor(max_workers=5)      #
17 for i in range(20):
18     t_pool.submit(func, i).add_done_callback(call_back)
19
20 >>>0
21 结果是 0
22 4
23 3
24 结果是 9
25 结果是 16
26  ...

View Code

转载于:https://www.cnblogs.com/pontoon/p/10449651.html

02python 中的线程相关推荐

  1. Java5中的线程池实例讲解

    Java5增加了新的类库并发集java.util.concurrent,该类库为并发程序提供了丰富的API多线程编程在Java 5中更加容易,灵活.本文通过一个网络服务器模型,来实践Java5的多线程 ...

  2. SpringBoot中的线程池,你真的会用么?

    点击关注公众号,Java干货及时送达 来源:blog.csdn.net/m0_37701381/article/details/81072774 前言 前两天做项目的时候,想提高一下插入表的性能优化, ...

  3. Delphi中的线程类

    Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchr ...

  4. Spring Bean 中的线程安全

    在 使用Spring框架时,很多时候不知道或者忽视了多线程的问题.因为写程序时,或做单元测试时,很难有机会碰到多线程的问题,因为没有那么容易模拟多线 程测试的环境.但如果不去考虑潜在的漏洞,它就会变成 ...

  5. 一个线程池中的线程异常了,那么线程池会怎么处理这个线程?

    一个线程池中的线程异常了,那么线程池会怎么处理这个线程? 参考文章: (1)一个线程池中的线程异常了,那么线程池会怎么处理这个线程? (2)https://www.cnblogs.com/fangua ...

  6. java mina多线程_mina2中的线程池

    一.Mina中的线程池模型 前面介绍了Mina总体的层次结构,那么在Mina里面是怎么使用Java NIO和进行线程调度的呢?这是提高IO处理性能的关键所在.Mina的线程调度原理主要如下图所示: A ...

  7. Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失(续)

    前言 上篇文章<Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失>我们对ThreadLocal数据丢失进行了详细的分析,并通过代码的方式复现了这个问题. ...

  8. python 在主线程开线程_Python开启线程,在函数中开线程的实例

    逻辑处理上分成了多个模块,为了提高效率,前一个模块处理完调用后一个模块操作时使用多线程 我这里遇到的情形是前面取数据后面存到mysql,发现单线程效率很低,改为取数据后开线程存到mysql 开启线程之 ...

  9. Tomcat中的线程池(APR和ThreadPool)

    一.容器简化了程序员自身的多线程编程. 各种Web容器,如Tomcat,Resion,Jetty等都有自己的线程池(可在配置文件中配置),所以在客户端进行请求调用的时候,程序员不用针对Client的每 ...

最新文章

  1. cli vue 卸载,vue Cli 环境删除与重装教程 - 版本文档
  2. 只需三分钟!只需创建一个vuex.js文件,让你马上学会使用Vuex,尽管Vuex是个鸡肋!(扔掉store文件夹和里面的index、getters、actions、mutations等js文件吧!)
  3. ”计算机操作系统“学习笔记1
  4. a表两个字段都与b表一个字段关联_Oracle系列第二章----表,精彩延续。。。
  5. 爬虫数据采集技术趋势-智能化解析
  6. Nacos如何支撑阿里内部数十万服务注册压力?
  7. Method 'GET_ENTITYSET' not implemented in data provider class - correct case
  8. LOJ:出纳员问题(差分约束)
  9. 【小程序】【Tips】等待服务器的反馈的定时、间隔运行的正确方法
  10. Android 系统性能优化(39)---Android内存优化之三:打开MAT中的Bitmap原图
  11. 【linux】WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
  12. java 文件追加文本_Java追加文件内容的三种方法
  13. mysql xml字段转json格式_mysql将xml数据或者json数据转换为表格。
  14. EJBCA 在windows上的安装
  15. zabbix安装 监控mysql
  16. 西门子PLC 和v90 伺服变频器G120通讯
  17. Markdown常用快捷键
  18. C++ 使用Poco库实现XML的读取和写入
  19. 如何从根本上防止服务器被攻击
  20. hi3516配置wifi_hi3516上wifi驱动有几率加载失败

热门文章

  1. go程序打包:针对不同OS打包成不同的可执行文件
  2. linux centos/debian下hadoop2.6.5单机伪分布式安装
  3. python pyyaml模块使用示例:读取yaml文件内容
  4. Rancher添加主机及对主机中的Docker容器进行图形化管理
  5. Apollo后台配置:命名空间Namespqce配置及代码读取
  6. node运行swagger-editor
  7. kibana操作elasticsearch:查看索引库数据
  8. Linux 切换目录命令cd
  9. 【客户下单】自动分单逻辑
  10. ActiveMQ入门案例-生产者代码实现