上一篇博客讲了进程、线程、协程和GIL的基本概念,这篇我们来说说在以下三点:

  1> python中使用threading库来创建线程的两种方式

  2> 使用Event对消来判断线程是否已启动

  3> 使用Semaphore和BoundedSemaphore两个类分别来控制线程的并发数以及二者之间的区别。

  如果想要了解基本概念,请移步我的上一篇博客:https://www.cnblogs.com/ss-py/p/10236125.html

正文:

  利用threading库来在创建一个线程:

from threading import Thread

def run(name):
print('我是: %s' % (name)) if __name__ == '__main__':
t = Thread(target=run, args=('线程1号',))
t.start()

  运行结果:  

  

  首先,创建一个Thread线程,并用target=run来指定这个线程需要执行那个run方法,然后将run方法所需的参数以args参数的形式传递过去,

  请注意,args所传的参数是一个元组(tuple)类型,因此即使元组中只有一个参数,也要在这个参数后再加一个逗号,如 args=('线程1号',)

  

  而且,当我们创建一个线程对象后,这个线程并不会立即执行,除非调用它的star()方法,当调用star()方法后,这个线程会调用你使用target传给他的函数,

  并将args中的参数传递给这个函数。

  Python中的线程会在一个单独的系统级线程中执行(如一个POSIX线程或一个Windows线程),这些线程全部由操作系统进行管理,线程一旦启动,

  它将独立运行直至目标函数运行结束。

  我们可以调用is_alive()方法来进行判断该线程是否在运行(is_alive()方法返回True或者False):

  我们知道,进程是依赖于线程来执行的,所以当我们的py文件在执行时,我们可以理解为有一个主线程在执行,当我们创建一个子线程时,就相当于当前程序一共有两个线程在执行。当子线程被创建后,主线程和子线程就独立运行,相互并不影响。

  代码如下:

 import time
from threading import Thread def run(name):
time.sleep(2)
print('我是: %s' % (name)) if __name__ == '__main__':
t = Thread(target=run, args=('子线程',))
t.start()
print('我是主线程')

  执行结果:

  

  在代码的第9行,创建了一个线程t,让它来执行run()方法,这时,程序中就有了两个线程同时存在、各自独立运行,默认的,主线程是不会等待子线程的运算结果的,所以主线程继续向下执行,打印L“我是主线程”,而子线程在调用run()方法时sleep了两秒钟,之后才打印出“我是子线程”

  当然,我们可以手动的调用join()方法来让主线程等待子线程运行结束后再向下执行:

 import time
from threading import Thread def run(name):
time.sleep(2)
print('我是: %s' % (name)) if __name__ == '__main__':
t = Thread(target=run, args=('子线程',))
t.start()
t.join()
print('我是主线程')

  

  这时,程序在进行到第十行后,主线程就卡住了,它在等待子线程运行结束,档子线程运行结束后,主线程才会继续向下运行,直至程序退出。

  但是,但是,无论主线程等不等待子线程,Python解释器都会等待所有的线程都终止后才会退出。也就是说,无论主线程等不等待子线程,这个程序最终都

  会运行两秒多,因为子线程sleep了两秒。

  所以,当遇到需要长时间运行的线程或者是需要一直在后台运行的线程时,可以将其设置为后台线程(守护线程)daemon=True,如:

t = Thread(target=run, args=('子线程',), daemon=True)

  守护线程,顾名思义是守护主线程的线程,他们是依赖于主线程而存在的,当主线程执行结束后,守护线程会被立即注销,无论该线程是否执行结束。

  当然,我们也可以利用join()来使主线程等待主线程。

  使用threading库来创建线程还有一种方式:

from threading import Thread

class CreateThread(Thread):

    def __init__(self):
super().__init__() def run(self):
print('我是子线程!') t = CreateThread()
t.start()

  在开始t = Thread(target=run, args=('子线程',))这种方式调用方法时子线程调用的run方法的这个方法名是我随便起的,实际上叫什么都行,

  但是以继承Thread类方式实现线程时,线程调用的方法名必须是run() 这个是程序写死的。

  使用Event对象判断线程是否已经启动

  threading库中的Event对象包含一个可由线程来设置的信号标志,它允许线程等待某些事件的发生。

  初始状态时,event对象中的信号标志被设置为假,如果有一个线程等待event对象,且这个event对象的标志为假,那么这个线程就会一直阻塞,直到该标志为真。如果将一个event对象的标志设置为真,他将唤醒所有等待这个标志的线程,如果一个线程等待一个被设置为真得Event对象,那么它将忽略这个事件,继续向下执行。  

  Event (事件) 定义了一个全局的标志Flag,如果Flag为False,当程序执行event.wait()时就会阻塞,当Flag为True时,程序执行event.wait()时便不会阻塞:

  event.set():  将标志Flag设置为True, 并通知所有因等待该标志而处于阻塞状态的线程恢复运行。

  event.clear(): 将标志Flag设置为False

  event.wait():  判断当前标志状态,如果是True则立即返回,否则线程继续阻塞。

  event.isSet(): 获取标志Flag状态: 返回True或者False

 from threading import Thread, Event

 def run(num, start_evt):
if int(num) >10:
start_evt.set()
else:
start_evt.clear()
start_evt = Event() if __name__ == '__main__':
num = input("请输入数字>>>")
t = Thread(target=run, args=(num, start_evt,))
t.start()
start_evt.wait() # 主线程获取Event对象的标志状态,若为True,则主线程继续执行,否则,主线程阻塞
print("主线程继续执行!")

  上边这段代码:当输入的数字大于10时,将标志设置为True,主程序继续执行,当小于或者等于10时,将标志设为False(默认为False),主线程阻塞。

     

  值得注意的是:当Event对象的标志被设置为True时,他会唤醒所有等待他的线程,如果只想唤醒某一个线程,最好使用信号量。

  信号量

  信号量,说白了就是一个计数器,用来控制线程的并发数,每次有线程获得信号量的时候(即acquire())计数器-1,释放信号量时候(release())计数器+1,计数器为0的时候其它线程就被阻塞无法获得信号量

  acquire()   # 设置一个信号量

  release()   # 释放一个信号量

  python中有两个类实现了信号量:(Semaphore和BoundedSemaphore)

  Semaphore和BoundedSemaphore的相同之处:

    通过: threading.Semaphore(3) 或者 threading.BoundedSemaphore(3) 来设置初始值为3的计数器

    执行acquire() 计数器-1,执行release() 计数器+1,当计数器为0时,其他线程均无法再获得信号量从而阻塞

import threading

se = threading.BoundedSemaphore(3)

for i in range(5):
se.acquire()
print('信号量被设置') for j in range(10):
se.release()
print('信号量被释放了')

  执行结果:

import threading

se = threading.Semaphore(3)

for i in range(5):
se.acquire()
print('信号量被设置') for j in range(10):
se.release()
print('信号量被释放了')

  执行结果:

  可以看到,无论我们用那个类创建信号量,当计数器被减为0时,其他线程均会阻塞。

  这个功能经常被用来控制线程的并发数:

  没有设置信号量:

import time
import threading num = 3 def run():
time.sleep(2)
print(time.time()) if __name__ == '__main__':
t_list = []
for i in range(20):
t = threading.Thread(target=run)
t_list.append(t)
for i in t_list:
i.start()

  执行结果:20个线程几乎在同时执行,,如果主机在执行IO密集型任务时执行这种程序时,主机有可能会宕机,

  但是在设置了信号量时,我们可以来控制同一时间同时运行的线程数:

import time
import threading num = 3 def run():
se.acquire() # 添加信号量
time.sleep(2)
print(time.time())
se.release() # 释放一个信号量 if __name__ == '__main__':
t_list = []
se = threading.Semaphore(5) # 设置一个大小为5的计数器(同一时间,最多允许5个线程在运行)
   # se = threading.BoundedSemaphore(5) 
    for i in range(20):
t = threading.Thread(target=run)
t_list.append(t)
for i in t_list:
i.start()

  这时,我们给程序加上信号量,控制它在同一时间内,最多只有5个线程在运行。

  

  两者之间的差异性:

    当计数器达到设定好的上线时,BoundedSemaphore就无法进行release()操作了,Semaphore没有这个限制,它会抛出异常。

  

import threading

se = threading.Semaphore(3)

for i in range(3):  # 将计数器值减为0
se.acquire(3) for j in range(5): # 将计数器值加至5
se.release()
print('信号量被释放了')

  运行结果:

  

import threading

se = threading.BoundedSemaphore(3)

for i in range(3):  # 将计数器值减为0
se.acquire(3) for j in range(5): # 将计数器值加至5
se.release()
print('信号量被释放了')

  运行结果:

  抛异常了:信号量被释放太多次。。。

  好了,这篇文章的就先写到这里,下一篇文章我会讲解关于线程间通信、线程加锁等问题

想了解更多Python关于爬虫、数据分析的内容,欢迎大家关注我的微信公众号:悟道Python

  

  

  

 

进程、线程、协程和GIL(二)的更多相关文章

  1. 线程、进程、协程和GIL(三)

    上一篇文章介绍了:创建线程的两种方式.Event对象判断线程是否启动.利用信号量控制线程并发. 博客链接:线程.进程.协程和GIL(二) 这一篇来说说线程间通信的那些事儿: 一个线程向另一个线程发送数 ...

  2. 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型

    本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...

  3. Python 进程线程协程 GIL 闭包 与高阶函数(五)

    Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 ​ 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...

  4. 进程&线程&协程

    进程  一.基本概念 进程是系统资源分配的最小单位, 程序隔离的边界系统由一个个进程(程序)组成.一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stac ...

  5. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  6. Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程

    1 引言 并发.并行.串行.同步.异步.阻塞.非阻塞.进程.线程.协程是并发编程中的常见概念,相似却也有却不尽相同,令人头痛,这一篇博文中我们来区分一下这些概念. 2 并发与并行 在解释并发与并行之前 ...

  7. 线程、进程、协程和GIL(一)

    参考链接:https://www.cnblogs.com/alex3714/articles/5230609.html https://www.cnblogs.com/work115/p/562027 ...

  8. python的进程/线程/协程

    1.python的多线程 多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有 ...

  9. python-socket和进程线程协程(代码展示)

    socket # 一.socket # TCP服务端 import socket # 导入socket tcp_sk = socket.socket() # 实例化一个服务器对象 tcp_sk.bin ...

  10. day 7-22 进程,线程,协程

    一.什么是进程 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动.它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体.它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理 ...

随机推荐

  1. Java中的阻塞队列-ConcurrentLinkedQueue

    http://ifeve.com/concurrentlinkedqueue/ 1.    引言 在并发编程中我们有时候需要使用线程安全的队列.如果我们要实现一个线程安全的队列有两种实现方式一种是使用 ...

  2. CSS中的样式层叠机制Cascade

    CSS中的样式层叠机制Cascade 一.样式冲突   样式冲突是CSS渲染过程要解决的一个关键问题,样式冲突主要由两个原因造成: 元素包含了不同对象所赋予的样式:浏览器.用户.作者.其中,浏览器样式 ...

  3. 文本框textarea根据输入内容自适应高度 和输入中文和数字换行解决方法

    textarea内容可从后台读取或手动输入,常规输入后中文和数字会出现换行问题 <style> #textarea { display: block; margin: 0 auto; ov ...

  4. python实现各种排序

    1.冒泡排序: # -*- coding: utf-8 -*- def BubbleSort(a): n=len(a) for i in range(0,n-1): swapped=False for ...

  5. HTML5开发,背后的事情你知道吗?

    现在的H5越来越受到企业或者是开发者的一个大力的追捧,已经成为网络推广必不可少的一个使用的工具,相信还有很多朋友现在都不知道H5是个什么东西,本文将为大家讲的是关于H5一些分类的问题,让你进一步的去学 ...

  6. 笨办法学Python(二十一)

    习题 21: 函数可以返回东西 你已经学过使用 = 给变量命名,以及将变量定义为某个数字或者字符串.接下来我们将让你见证更多奇迹.我们要演示给你的是如何使用 = 以及一个新的 Python 词汇ret ...

  7. github设置添加SSH(转载自:破男孩)

    注:本文来源于 破男孩 博客(http://www.cnblogs.com/ayseeing/p/3572582.html)能切实解决问题. 很多朋友在用github管理项目的时候,都是直接使用htt ...

  8. 2018.7.23 oracle中的CLOB数据类型

    Oarcle中的LOB类型 1.在Oracle中,LOB(Large Object,大型对象)类型的字段现在用得越来越多了.因为这种类型的字段,容量大(最多能容纳4GB的数据),且一个表中可以有多个这 ...

  9. 2017.11.1 微型计算机原理与接口技术-----第七章 中断系统与8237A DMA控制器

    第七章 微型计算机原理与接口技术-----中断系统与8237A DMA控制器 (1)数据传送的两种方式:中断方式和直接存储器存取方式(DMA):中断是微处理器与外部设备交换信息的一种方式:DMA是存储 ...

  10. 2017.10.16 java中getAttribute和getParameter的区别

    (1)getAttribute:表示得到 域中的对象 返回的是OBJ类型;  getParameter:表示 得到 传递的参数 返回的是String类型; 也就是getAttribute获得的值需要进 ...