今日要整理的内容有

1. 操作系统中线程理论

2.python中的GIL锁

3.线程在python中的使用

开始今日份整理

1. 操作系统中线程理论

  1.1 线程引入背景

  之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

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

  进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

  如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。

  现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。

注意:进程是资源分配的最小单位,线程是cpu调度的最小单位,每一个进程中至少一个线程

  1.2 进程与线程的区别

进程与线程之间的区别主要是有以下几点

  • 地址空间与其他资源(如打开文件):进程之间相互独立,同一个进程内线程相互共享,某个进程内的线程其他进程不可见。
  • 通信:进程之间通信需要IPC,线程之间通信可以直接调用进程的数据段(全局变量),进程之间的通信如果要保证数据安全,需要一定的IPC机制以及数据安全机制
  • 调度和切换:由于进程的开启需要较多资源调度,所以进程的上下文的切换相比线程的上下文的切换要慢很多。
  • 在多线程的操作系统中,进程只是一个资源分配的单位,并不是操作系统执行的实体

  1.3 线程的特点

在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。

  • 轻型实体:线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
  • 独立调度以及分配的基本单位:在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
  • 共享进程资源:程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
  • 可并发操作:在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

补充TCB的内容

  1. TCB包括以下信息:
  2. 1)线程状态。
  3. 2)当线程不运行时,被保存的现场资源。
  4. 3)一组执行堆栈。
  5. 4)存放每个线程的局部变量主存区。
  6. 5)访问同一个进程中的主存和其它资源。
  7. 用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。

1.4 使用线程的使用场景

  开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。

1.5 内存中的线程

  多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。

  而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。

  不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,一个线程可以访问另外一个线程的内存地址,大家都是共享的,一个线程干死了另外一个线程的内存,那纯属程序员脑子有问题。

  类似于进程,每个线程也有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。

  线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:

  1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程

  2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?

  因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。

2.python中的GIL锁

  2.1 python如何使用GIL锁

  全局解释器锁GIL

  Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
  对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

  在多线程环境中,Python 虚拟机按以下方式执行:

  a、设置 GIL;

  b、切换到一个线程去运行;

  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

  d、把线程设置为睡眠状态;

  e、解锁 GIL;

  d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

  python中线程不能同时访问cpu的确是一个缺点,不过我们在写代码的时候可以判断这个代码块是多I/O还是多计算,多I/O即使是单cpu也是可以顺畅执行的,不过对于高计算的则开启多进程然后进程中开启多线程并发的执行计算,对于python还有一个原因是他是解释性语言,在编译的同时不知道下面的代码的具体执行情况,不能多cpu同时调用。

3.线程在python中的使用

3.1线程的俩种方式

方式一:

  1. from threading import Thread
  2. import time
  3. import os
  4.  
  5. def func(i):
  6. time.sleep(1)
  7. print('子进程%s!'%i,os.getpid())
  8.  
  9. for i in range(10):
  10. t = Thread(target= func,args=(i,))
  11. t.start()
  12.  
  13. print('主进程!',os.getpid())

方式二:

  1. #自建类的方式
  2. from threading import Thread
  3. import time
  4. import os
  5.  
  6. class MyThread(Thread):
  7. def __init__(self,name):
  8. super().__init__()
  9. self.name = name
  10.  
  11. def run(self):#和进程一样,自建类,必须用run函数
  12. time.sleep(1)
  13. print('子进程%s'%self.name,os.getpid())
  14.  
  15. if __name__ == '__main__':
  16. print('主线程!',os.getpid())
  17. for i in range(10):
  18. p = MyThread(i)
  19. p.start()

  执行代码我们会发现,肉眼所看基本上所有的线程都是一起建立成功,这个时候就会有疑问,不是有GIL全局解释锁吗,不应该每隔一秒才创建一个线程吗,看图

步骤如下

  1. for循环,创建一个线程
  2. 线程经过GIL锁,获得一个cpu去执行
  3. 执行线程中的代码,碰到time.sleep,执行代码后将代码从运行态丢到阻塞队列,归还GIL锁的钥匙
  4. 线程2创建并重复执行2和3这俩个步骤
  5. 由于cpu运算速度极快,我们肉眼可以看到十个线程在间隔1秒左右后,基本同时创建了10个线程

注意:程序中的IO操作是不占用全局解释器锁和CPU的

  3.2 threading内的其他用法

  3.2.1 join方法

  线程中join方法和进程中的join方法时一致的,都是对主线程的阻塞,直到子线程的中的代码执行结束

看代码

  1. from threading import Thread
  2. import time
  3.  
  4. def func(i):
  5. time.sleep(1)
  6. print('子线程%s'%i)
  7.  
  8. t_list =[]
  9. for i in range(10):
  10. t = Thread(target= func,args=(i,))
  11. t_list.append(t)
  12. t.start()
  13.  
  14. for t in t_list:
  15. t.join()
  16. print('所有线程已经全部执行完毕!')

3.2.2 守护线程

  守护线程和守护进程是差不多的,只是开启的方式一个是参数一个是内置方法

  1. from threading import Thread
  2. import time
  3.  
  4. def main():
  5. print('主线程开始!')
  6. time.sleep(3)
  7. print('主线程结束')
  8.  
  9. def func():
  10. print('子线程开始')
  11. time.sleep(5)
  12. print('子线程结束')
  13.  
  14. def daemon():
  15. while True:
  16. time.sleep(1)
  17. print('我活的很好!')
  18.  
  19. t = Thread(target= daemon,)
  20. t.setDaemon(True)#设定为守护线程的方法
  21. t.start()
  1.  
  1. t1 = Thread(target= main,)
    t2 = Thread(target= func,)
    t1.start()
    t2.start()

  看结果会发现,守护线程不紧守护了主线程,同时守护了子线程,可以得出以下结果

  守护线程和守护进程不同,守护线程会守护主线程直到主线程结束,如果这个时候主线程要等待子线程的执行结束,那么守护线程同时对子线程进行守护

3.2.3 数据共享

  同一个进程内的线程由于是共享一个资源空间,所以一个进程内的线程是天生共享资源,看代码

  1. # from threading import Thread
  2. #
  3. # n = 100
  4. # def func():
  5. # global n
  6. # n-=1
  7. #
  8. # t_list =[]
  9. # for i in range(100):
  10. # t = Thread(target= func,)
  11. # t_list.append(t)
  12. # t.start()
  13. # for i in t_list:
  14. # i.join()
  15. # print(n)

3.2.4 查看线程id

俩种方式

方式一:

  1. #方式一:面向对象的方式
  2.  
  3. from threading import Thread
  4. import os
  5.  
  6. class MyThread(Thread):
  7. def __init__(self,name):
  8. super().__init__()
  9. self.name = name
  10.  
  11. def run(self):
  12. print('子进程%s'%self.name,self.ident,os.getpid())
  13.  
  14. for i in range(10):
  15. t = MyThread(i)
  16. t.start()

方式二:

  1. #方式二:普通方式
  2.  
  3. from threading import Thread,currentThread
  4. import os
  5.  
  6. def func(i):
  7. print('子进程%s'%i,currentThread().ident,os.getpid())
  8.  
  9. for i in range(10):
  10. t = Thread(target= func,args= (i,))
  11. t.start()

3.2.5 threading的其他方法

  1. from threading import Thread,currentThread,enumerate,active_count
  2. import os
  3. import time
  4.  
  5. def func(i):
  6. time.sleep(1)
  7. print('子进程%s'%i,currentThread().ident,os.getpid())
  8.  
  9. for i in range(10):
  10. t = Thread(target= func,args= (i,))
  11. t.start()
  12. print(enumerate())#枚举查看活着的线程
  13. print(active_count())#查看活着的线程数量,子线程数量加上主线程

3.3 线程中的锁

3.3.1 互斥锁

  虽说一个进程内线程都是共享资源,,但是实际中我们还是在线程中如果有+=、-=、*=、/=等操作还是要加锁,先看例子,为什么会这样

  1. from threading import Thread
  2. import time
  3.  
  4. n = 0
  5. def func():
  6. global n
  7. count = n
  8. time.sleep(0.1)#用阻塞模拟时间片轮转
  9. n = count+1
  10. t_list =[]
  11. for i in range(100):
  12. t = Thread(target= func)
  13. t_list.append(t)
  14. t.start()
  15. for i in t_list:
  16. i.join()
  17. print(n)
  18. #结果为1
  19. #如果将阻塞的代码注释后就会变成100
  20. #对于操作系统来说,一定数量的线程并不会出现问题,不过在实际使用中为了安全性,建议不要做,需要加锁
  21. #修改后的代码
  22. from threading import Thread,Lock
  23.  
  24. n = 0
  25. def func(lock):#加锁操作
  26. global n
  27. with lock:
  28. n +=1
  29.  
  30. t_list =[]
  31. lock = Lock()
  32. for i in range(100):
  33. t = Thread(target= func,args=(lock,))
  34. t_list.append(t)
  35. t.start()
  36. for i in t_list:
  37. i.join()
  38. print(n)
  39. #结果:100
  40. #这杨不管如何,结果都是100

结论:

  • 没有多个线程操作同一个变量的时候 就可以不加锁
  • 如果你是执行基础数据类型的内置方法 :lst.append,lst.pop,lst.extand,lst.remove,dic.get['key']都是线程安全的
  • 补充:dis模块 可以把你的python代码翻译成cpu指令

3.3.2 迭代锁

3.3.2.1 死锁问题

  要说迭代锁首先要说死锁问题,而说明死锁问题就要说一下著名的科学家吃面问题

  科学家吃面问题:四个科学家吃面,桌子上只有一盘面和一把叉子,只有在同时拿到面和叉子,才可以迟到,拿到面或者叉子是不能做任何事情,下面模拟下科学家吃面这个问题,代码如下

  1. #死锁版
  1. from threading import Thread,Lock
  2. import time
  3. nooodle_lock = Lock()
  4. fork_lock = Lock()
  5.  
  6. def eat1(name):
  7. nooodle_lock.acquire()
  8. print('%s 拿到了面条!'%name)
  9. fork_lock.acquire()
  10. print('%s 拿到了叉子!' % name)
  11. print('%s 开始吃面'%name)
  12. time.sleep(1)#模拟吃面
  13. fork_lock.release()
  14. print('%s 放下了叉子!' % name)
  15. nooodle_lock.release()
  16. print('%s 放下了面条'% name)
  17.  
  18. def eat2(name):
  19. fork_lock.acquire()
  20. print('%s 拿到了叉子!' % name)
  21. nooodle_lock.acquire()
  22. print('%s 拿到了面条!' % name)
  23. print('%s 开始吃面'%name)
  24. time.sleep(1)#模拟吃面
  25. nooodle_lock.release()
  26. print('%s 放下了面条'% name)
  27. fork_lock.release()
  28. print('%s 放下了叉子!' % name)
  29.  
  30. t1 = Thread(target= eat1,args=('饭桶1',))
  31. t2 = Thread(target= eat2,args=('饭桶2',))
  32. t3 = Thread(target= eat1,args=('饭桶3',))
  33. t4 = Thread(target= eat2,args=('饭桶4',))
  34. t1.start()
  35. t2.start()
  36. t3.start()
  37. t4.start()
  38. #结果
  39. 饭桶1 拿到了面条!
  40. 饭桶1 拿到了叉子!
  41. 饭桶1 开始吃面
  42. 饭桶1 放下了叉子!
  43. 饭桶2 拿到了叉子!
  44. 饭桶1 放下了面条
  45. 饭桶3 拿到了面条!
  46. #饭桶2 和饭桶3 各拿面条和叉子互不放弃,这样就造成了死锁

3.3.2.2 迭代锁

  根据上面的例子,我们就需要引入一个迭代锁的概念

  正常的互斥锁,就相当于一个门,一把钥匙,谁抢到谁进去,如图

  而迭代锁就是一个门,可是门口挂着一串钥匙,拿到这一串钥匙的人就可以进入门后面的门,如图

  把上面的科学家吃面问题解决一下

  1. #迭代锁版
  1. from threading import Thread,RLock
  2. import time
  3. nooodle_lock =fork_lock= RLock()
  4.  
  5. def eat1(name):
  6. nooodle_lock.acquire()
  7. print('%s 拿到了面条!'%name)
  8. fork_lock.acquire()
  9. print('%s 拿到了叉子!' % name)
  10. print('%s 开始吃面'%name)
  11. time.sleep(1)#模拟吃面
  12. fork_lock.release()
  13. print('%s 放下了叉子!' % name)
  14. nooodle_lock.release()
  15. print('%s 放下了面条'% name)
  16.  
  17. def eat2(name):
  18. fork_lock.acquire()
  19. print('%s 拿到了叉子!' % name)
  20. nooodle_lock.acquire()
  21. print('%s 拿到了面条!' % name)
  22. print('%s 开始吃面'%name)
  23. time.sleep(1)#模拟吃面
  24. nooodle_lock.release()
  25. print('%s 放下了面条'% name)
  26. fork_lock.release()
  27. print('%s 放下了叉子!' % name)
  28.  
  29. t1 = Thread(target= eat1,args=('饭桶1',))
  30. t2 = Thread(target= eat2,args=('饭桶2',))
  31. t3 = Thread(target= eat1,args=('饭桶3',))
  32. t4 = Thread(target= eat2,args=('饭桶4',))
  33. t1.start()
  34. t2.start()
  35. t3.start()
  36. t4.start()
  37. #结果就是每一个人都可以吃到面了,只是Lock改为RLock

总结:

  • 死锁现象:使用了多把锁在一个线程内进行了多次Acquire导致了不可恢复的阻塞。
  • 形成原因: # 两个锁 锁了两个资源 我要做某件事 需要同时拿到这两个资源 多个线程同时执行这个步骤。
  • 递归锁(rlock)、互斥锁(lock):递归锁是不容易发生死锁现象的,互斥锁的使用不当容易发生死锁,递归锁可以快速的帮我们解决死锁问题。
  • 死锁的真正问题不在于互斥锁,而在于对于互斥锁的混乱使用,要想真正的解决死锁问题,还是要找出互斥锁的问题进行修正才能解决根本。

那么按照这个思路,我们可以改变一下上面的科学家吃面问题

#最终版

  1. from threading import Thread,Lock
  2. import time
  3. lock = Lock()
  4.  
  5. def eat1(name):
  6. lock.acquire()
  7. print('%s 拿到了面条!'%name)
  8. print('%s 拿到了叉子!' % name)
  9. print('%s 开始吃面'%name)
  10. time.sleep(1)#模拟吃面
  11. print('%s 放下了叉子!' % name)
  12. print('%s 放下了面条'% name)
  13. lock.release()
  14.  
  15. def eat2(name):
  16. lock.acquire()
  17. print('%s 拿到了叉子!' % name)
  18. print('%s 拿到了面条!' % name)
  19. print('%s 开始吃面'%name)
  20. time.sleep(1)#模拟吃面
  21. print('%s 放下了面条'% name)
  22. print('%s 放下了叉子!' % name)
  23. lock.release()
  24.  
  25. Thread(target= eat1,args=('饭桶1',)).start()
  26. Thread(target= eat2,args=('饭桶2',)).start()
  27. Thread(target= eat1,args=('饭桶3',)).start()
  28. Thread(target= eat2,args=('饭桶4',)).start()

  最终结论:使用迭代锁的时候,绝大多数是因为代码中各种各样的锁太多,机制混乱,在一头雾水的时候可以先用带带锁解决问题,然后在想着一点点的去解决问题!

小练习:基于多线程的socket的套接字,简易版

  1. #server端
  2. import socket
  3. from threading import Thread
  4.  
  5. sk = socket.socket()
  6. sk.bind(('127.0.0.1',8500))
  7. sk.listen()
  8.  
  9. def talk(conn,addr):
  10. print(addr)
  11. while True:
  12. conn.send(b'hello world!')
  13.  
  14. while True:
  15. conn,addr = sk.accept()
  16. Thread(target=talk,args=(conn,addr)).start()#将具体聊天的过程交给不同的线程去执行
  17.  
  18. #客户端
  19. import socket
  20. sk = socket.socket()
  21. sk.connect(('127.0.0.1',8500))
  22.  
  23. while True:
  24. msg = sk.recv(1024)
  25. print(msg)
  26.  
  27. sk.close()
  28.  
  29. #最后的结果就是服务器端可以接收多个客户端连接,每个客户端可以不受影响的去接收

3.4 线程队列

  首先说一下,一个进程中的多线程共用一块资源空间,为什么还需要用到队列,主要是有俩个方面

  • 多个线程之间维持一个数据先后的秩序
  • 线程模块的队列是线程之间数据安全的

不同于多进程中的有专门的队列模块,线程中使用队列就需要调用python中自带模块queue模块

3.4.1 线程队列中常用的方法

  1. #线程队列中的常用方法
  2. 1.队列的实例化:
  3. q = queue.Queue(num)#num可以为空,加上则设定队列的大小
  4.  
  5. 2.队列的放入以及取出:
  6. q.put() q.get()
  7. #他俩都是阻塞事件
  8.  
  9. 3.队列状态的判断
  10. q.size() q.empty() q.full()
  11. 在多进程中状态经常有误,很少去使用
  12.  
  13. 4.队列的快速取出以及快速放入
  14. import queue
  15. q = queue.Queue(1)
  16. q.put(1)
  17. try:
  18. print(q.put_nowait('abc'))#快速放入,不管队列是都满,如果满则丢弃,所以经常不用,队列满会报queue.Full错误
  19. except queue.Full:
  20. pass
  21.  
  22. try:
  23. print(q.get_nowait())#快速取出,不管队列是否空,空则报错queue.Empty
  24. except queue.Empty:
  25. pass

3.4.2 算法中的常用数据类型

在算法中有以下常用数据类型

  • 队列:先进先出FIFO
  • 栈:后进先出LIFO,主要是用在算法上
  • 堆:三角形的数据类型,越重要的数据在越在上面
  • 树:关系型的数据类型

3.4.3 其他补充队列

第一种:栈,后进先出

  1. import queue
  2.  
  3. q = queue.LifoQueue()#栈
  4. q.put(1)
  5. q.put(2)
  6. q.put(3)
  7. print(q.get())
  8. print(q.get())
  9. print(q.get())
  10. #结果
  11. 3
  12. 2
  13. 1

第二种:优先级队列,put的时候是一个元祖,按照元祖第一个元素的ASCII码的位置

  1. import queue
  2.  
  3. q = queue.PriorityQueue()
  4. q.put((2,'abc'))
  5. q.put((1,'bbb'))
  6. q.put((3,'ccc'))
  7. print(q.get())
  8. print(q.get())
  9. print(q.get())
  10. #结果
  11.  
  12. (1, 'bbb')
  13. (2, 'abc')
  14. (3, 'ccc')

3.5 线程池

  线程在最开始创建的时候是没有线程池的概念的,所以线程与进程不同的是没有专门的线程池的方法,需要调用其他模块

  Python标准模块--concurrent.futures,官方参考介绍文章

  https://docs.python.org/dev/library/concurrent.futures.html

concurrent.futures模块常用方法

  1. #1 介绍
  2. concurrent.futures模块提供了高度封装的异步调用接口
  3. ThreadPoolExecutor:线程池,提供异步调用
  4. ProcessPoolExecutor: 进程池,提供异步调用
  5. Both implement the same interface, which is defined by the abstract Executor class.
  6.  
  7. #2 基本方法
  8. #submit(fn, *args, **kwargs)
  9. 异步提交任务
  10.  
  11. #map(func, *iterables, timeout=None, chunksize=1)
  12. 取代for循环submit的操作
  13.  
  14. #shutdown(wait=True)
  15. 相当于进程池的pool.close()+pool.join()操作
  16. wait=True,等待池内所有任务执行完毕回收完资源后才继续
  17. wait=False,立即返回,并不会等待池内的任务执行完毕
  18. 但不管wait参数为何值,整个程序都会等到所有任务执行完毕
  19. submitmap必须在shutdown之前
  20.  
  21. #result(timeout=None)
  22. 取得结果
  23.  
  24. #add_done_callback(fn)
  25. 回调函数

3.5.1 线程模块

  1. #线程模块
  2. #1.普通建池
  3. from threading import currentThread
  4. from concurrent.futures import ThreadPoolExecutor
  5. import time
  6.  
  7. def func(i):
  8. time.sleep(1)
  9. print('子线程%s'%i,currentThread().ident)#查看线程名,以及线程id
  10.  
  11. tp = ThreadPoolExecutor(4)#建池
  12. for i in range(20):
  13. tp.submit(func,i)#就相当于进程池中的apply_async异步的提交任务
  14. tp.shutdown()#相当于进程池中的.close()与.join()
  15.  
  16. #2.map建池
  17. from threading import currentThread
  18. from concurrent.futures import ThreadPoolExecutor
  19. import time
  20.  
  21. def func(i):
  22. time.sleep(1)
  23. print('子线程%s'%i,currentThread().ident)#查看线程名,以及线程id
  24.  
  25. tp = ThreadPoolExecutor(4)#建池
  26. tp.map(func,range(20))
  27.  
  28. #3.获取返回值
  29. from threading import currentThread
  30. from concurrent.futures import ThreadPoolExecutor
  31. import time
  32.  
  33. def func(i):
  34. time.sleep(1)
  35. print('子线程%s'%i,currentThread().ident)#查看线程名,以及线程id
  36. return i**2
  37.  
  38. tp = ThreadPoolExecutor(4)#建池
  39. ret_l =[]
  40. for i in range(20):
  41. ret = tp.submit(func,i)#就相当于进程池中的apply_async异步的提交任务
  42. ret_l.append(ret)
  43. tp.shutdown()#相当于进程池中的.close()与.join()
  44.  
  45. for i in ret_l:
  46. print(i.result())#就相当于进程中的结果的get()
  47.  
  48. #4.回调函数
  49. from threading import currentThread
  50. from concurrent.futures import ThreadPoolExecutor
  51. import time
  52.  
  53. def func(i):
  54. time.sleep(1)
  55. print('子线程%s'%i,currentThread().ident)#查看线程名,以及线程id
  56. return i**2
  57.  
  58. def call_back(ret):
  59. print(ret.result())#ret只是获取到结果的对象,需要对对象进行result()
  60.  
  61. tp = ThreadPoolExecutor(4)#建池
  62. for i in range(20):
  63. tp.submit(func,i).add_done_callback(call_back)#就相当于进程池中的apply_async异步的提交任务,和进程池中的callback=函数名一致
  64. tp.shutdown()#相当于进程池中的.close()与.join()

3.5.2 进程模块

  1. #进程模块
  2. #1.普通建池
  3. from concurrent.futures import ProcessPoolExecutor
  4. import os
  5. import time
  6.  
  7. def func(i):
  8. time.sleep(1)
  9. print('子进程%s'%i,os.getpid())
  10.  
  11. if __name__ == '__main__':
  12. pp= ProcessPoolExecutor(4)
  13. for i in range(20):
  14. pp.submit(func,i)
  15. pp.shutdown()
  16.  
  17. #2.map建池
  18.  
  19. from concurrent.futures import ProcessPoolExecutor
  20. import os
  21. import time
  22.  
  23. def func(i):
  24. time.sleep(1)
  25. print('子进程%s'%i,os.getpid())
  26.  
  27. if __name__ == '__main__':
  28. pp= ProcessPoolExecutor(4)
  29. pp.map(func,range(20))
  30.  
  31. #3.返回值
  32. from concurrent.futures import ProcessPoolExecutor
  33. import os
  34. import time
  35.  
  36. def func(i):
  37. time.sleep(1)
  38. print('子进程%s'%i,os.getpid())
  39. return i**2
  40.  
  41. if __name__ == '__main__':
  42. pp= ProcessPoolExecutor(4)
  43. ret_l =[]
  44. for i in range(20):
  45. ret = pp.submit(func,i)
  46. ret_l.append(ret)
  47. pp.shutdown()
  48. for ret in ret_l:
  49. print(ret.result())
  50.  
  51. #4.回调函数
  52. from concurrent.futures import ProcessPoolExecutor
  53. import os
  54. import time
  55.  
  56. def func(i):
  57. time.sleep(1)
  58. print('子进程%s'%i,os.getpid())
  59. return i**2
  60.  
  61. def call_back(ret):
  62. print(ret.result())
  63.  
  64. if __name__ == '__main__':
  65. pp= ProcessPoolExecutor(4)
  66. for i in range(20):
  67. pp.submit(func,i).add_done_callback(call_back)
  68. pp.shutdown()

  最后:后期我们建池的话主要使用concurrent.futures,一是比较新,二是既可建线程池也可以建立进程池,方便使用!

总结:

  • 进程池:定义的进程数(cpu数量/cpu数量+1)
  • 线程池:定义的线程数(cpu数*5)

day21-多并发编程基础(二)的更多相关文章

  1. TCP与UDP比较 以及并发编程基础知识

    一.tcp比udp真正可靠地原因 1.为什么tcp比udp传输可靠地原因: 我们知道在传输数据的时候,数据是先存在操作系统的缓存中,然后发送给客户端,在客户端也是要经过客户端的操作系统的,因为这个过程 ...

  2. Pthread 并发编程(二)——自底向上深入理解线程

    Pthread 并发编程(二)--自底向上深入理解线程 前言 在本篇文章当中主要给大家介绍线程最基本的组成元素,以及在 pthread 当中给我们提供的一些线程的基本机制,因为很多语言的线程机制就是建 ...

  3. [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors

    [Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...

  4. python中并发编程基础1

    并发编程基础概念 1.进程. 什么是进程? 正在运行的程序就是进程.程序只是代码. 什么是多道? 多道技术: 1.空间上的复用(内存).将内存分为几个部分,每个部分放入一个程序,这样同一时间在内存中就 ...

  5. 并发编程(二)concurrent 工具类

    并发编程(二)concurrent 工具类 一.CountDownLatch 经常用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作. import java.util.concurren ...

  6. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  7. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  8. Java并发编程系列-(1) 并发编程基础

    1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...

  9. Java并发编程基础三板斧之Semaphore

    引言 最近可以进行个税申报了,还没有申报的同学可以赶紧去试试哦.不过我反正是从上午到下午一直都没有成功的进行申报,一进行申报 就返回"当前访问人数过多,请稍后再试".为什么有些人就 ...

  10. Java高并发编程基础三大利器之CountDownLatch

    引言 上一篇文章我们介绍了AQS的信号量Semaphore<Java高并发编程基础三大利器之Semaphore>,接下来应该轮到CountDownLatch了. 什么是CountDownL ...

随机推荐

  1. RDIFramework.NET ━ .NET快速信息化系统开发框架 V3.2-模块管理按子系统进行分类管理

    在RDIFramework.NET以往的框架中,模块管理界面展示了整个框架所管理的所有模块,如果系统过多,达几十个甚至上百个子系统时,管理起来就非常的麻烦,不光加载效率会很低,页面展示也会很不友好.框 ...

  2. [五]java函数式编程归约reduce概念原理 stream reduce方法详解 reduce三个参数的reduce方法如何使用

    reduce-归约 看下词典翻译: 好的命名是自解释的 reduce的方法取得就是其中归纳的含义 java8 流相关的操作中,我们把它理解 "累加器",之所以加引号是因为他并不仅仅 ...

  3. SpringBoot基础系列-使用日志

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996897.html SpringBoot基础系列-使用日志 概述 SpringBoot ...

  4. Docker跨主机通信(九)--技术流ken

    容器网络 在前面的博客中已经详细讲解了几种网络方案: none, host, bridge,user-defined.但是他们只是解决了单个主机间的容器的通信问题,并不能实现多个主机容器之间的通信.本 ...

  5. SpringCloud系列——Ribbon 负载均衡

    前言 Ribbon是一个客户端负载均衡器,它提供了对HTTP和TCP客户端的行为的大量控制.我们在上篇(猛戳:SpringCloud系列——Feign 服务调用)已经实现了多个服务之间的Feign调用 ...

  6. 2019/1.6 Javascript Cookie

    cookie 就是一个1存放数据的东西,存储量很小(只有4KB),存放在客户端和应用设备上. 应用场景 用户注册,用户登录,购物车 chrome浏览器计算机存放cookie的位置 C:\Users\A ...

  7. ES10特性详解

    摘要: 最新的JS特性. ES10 还只是一个草案.但是除了 Object.fromEntries 之外,Chrome 的大多数功能都已经实现了,为什么不早点开始探索呢?当所有浏览器都开始支持它时,你 ...

  8. js 高级程序设计(笔记)

    第二章 1.为了避免浏览器在呈现页面时出现明显的延迟,现代Web 应用程序一般都把全部JavaScript 引用放在<body>元素中页面内容的后面. 第三章 1.ECMAScript 中 ...

  9. Vsphere 回收未消使用的磁盘空间

    下载sdelete.exe 执行 sdelete.exe -z E: ,然后又恢复为原可用空间 关机   SHH进入物理主机,找到对应的虚机文件 执行vmkfstools -K test-Win200 ...

  10. ArcGIS for JavaScript学习(二)Server发布服务

    一 ArcGIS for Server 安装.配置 (1)双击setup (2)点击下一步完成安装 (3)配置 a 登录Manager 开始—>程序—>ArcGIS—>Manager ...