python线程,进程,协程

简介:

资料来源:https://www.cnblogs.com/alex3714/articles/5230609.html

http://python.jobbole.com/86406/

https://www.cnblogs.com/wupeiqi/articles/5040827.html

https://www.cnblogs.com/tkqasn/p/5700281.html

在此感谢前辈们的指导,以下均为纯手打

一,程序,进程,线程概念的区分

在计算机中,程序不能单独运行,必须把它分配到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。

一句话总结:程序是指令的集合,它是程序的静态描述文本;进程确是程序的一次执行活动,是动态概念

那么,线程是什么呢,它与进程有什么关系呢,我们看几个例子:

线程是执行上下文,它是CPU执行指令流所需的全部信息。

1,假设你正在读一本书,现在你想休息一下,但你希望能够回来,从你停下来的确切地点恢复阅读。一个实现的方法就是记下页码,行数和字数。所以你阅读书的执行上下文是这3个数字。

2,如果你有一个室友,而且她使用相同的技巧,她可以在你不使用的时候拿书,然后从她停止阅读的地方继续阅读。然后你可以收回它,从你原来的地方恢复它。

因此执行上下文(因此线程)由CPU寄存器的值组成。

综上:线程不同于进程。线程是执行的上下文,而进程是与计算相关联的一堆资源。一个进程可以有一个或多个线程(资源当中肯定不止一个上下文)。

关于进程与线程还有几个重要的区别

1,与进程相关的资源包括内存页(进程中的所有线程对内存具有相同的视图)、文件描述符(例如,打开套接字)和安全凭据(例如启动进程的用户ID)。

2,很容易创建新线程;新进程需要重复父进程。

3,线程可以对相同进程的线程进行相当的控制;进程只能对子进程进行控制。

4,对主线程的更改(取消、优先级更改等)可能影响进程的其他线程的行为;对父进程的更改不会影响子进程。

二:有了进程,为什么需要线程(拓展)

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

2,进程在执行过程中,如果发生阻塞,那么进程就会挂起,无法执行程序。

三,道路从线程开始:

threading 模块提供的常用方法: 
  threading.currentThread(): 返回当前的线程变量。 
  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 
  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

threading 模块提供的常量:

  threading.TIMEOUT_MAX 设置threading全局超时时间。

线程的方法:

  • start            线程准备就绪,等待CPU调度

  • setName      为线程设置名称

  • getName      获取线程名称

  • setDaemon   设置为后台线程或前台线程(默认)
                       如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
                        如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

  • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义

  • run              线程被cpu调度后自动执行线程对象的run方法

接下来,围绕这几个方法,和线程的两种调用方式进行讲解

1,直接调用:

import threading
import time
def sayhi(num):
   print("running on number:%s"%num)
   time.sleep(3)
if __name__=='__main__':
   t1=threading.Thread(target=sayhi,args=(1,))#线程实例,逗号必须加
   t2 = threading.Thread(target=sayhi, args=(2,))  # 线程实例
   t1.start()#启动线程
   t2.start()#启动另一个线程
   print(t1.getName())

   print(t2.getName())

# 构造方法:
# Thread(group=None, target=None, name=None, args=(), kwargs={})

#   group: 线程组,目前还没有实现,库引用中提示必须是None;
#   target: 要执行的方法;
#   name: 线程名;

#   args / kwargs: 要传入方法的参数。

再来看一个间接调用(继承式调用):

import threading
import time
class MyThread(threading.Thread):
   def __init__(self, num):
       threading.Thread.__init__(self)
       self.num = num
   def run(self):  # 定义每个线程要运行的函数

       print("running on number:%s" % self.num)

       time.sleep(3)
if __name__ == '__main__':
   t1 = MyThread(1)
   t2 = MyThread(2)
   t1.start()
   t2.start()

接下来对这两个调用进行深究

一下子开五十个进程,看看时间多少

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done", n)
start_time=time.time()
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()

print("cost:",time.time()-start_time)

观察它所消耗的时间是

摘取部分结果

task t-46

task t-47

task t-48

task t-49

------------all threads has finished

cost: 0.0070035457611083984

task done t-2

task done t-1

task done t-0

task done t-8

为什么会出现这种情况呢,是因为主线程与子线程无关,不能在主线程中测子线程,因为主线程会执行自己的,,这里可能说的有点抽象,emmmmn,其实我也是在把这遍代码,码完后,才有些懂得。

主线程可以看成是:

import threading
import time
def run(n):
   print("task",n

start_time=time.time()
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()

print("cost:",time.time()-start_time)

这段代码集合(把run代码中的sleep去掉了),他只会执行自己的,不会等子线程的结束,所以将task打完后就把时间打印出来

那么如何让主线程等待呢:

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done",threading.current_thread())#返回到当前线程的对象
start_time=time.time()
t_objs=[]
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()
   t_objs.append(t1)
for t in t_objs:
   t.join()#结束
print("------------all threads has finished",threading.current_thread())

print("cost:",time.time()-start_time)#主线程与子线程无关,所以不能在主线程中测子线程

部分结果展示:

task done <Thread(Thread-34, started 3940)>

task done <Thread(Thread-33, started 6752)>

task done <Thread(Thread-44, started 10280)>

task done <Thread(Thread-43, started 14988)>

task done <Thread(Thread-42, started 7344)>

task done <Thread(Thread-41, started 8984)>

task done <Thread(Thread-40, started 9972)>

task done <Thread(Thread-48, started 12672)>

task done <Thread(Thread-47, started 9012)>

task done <Thread(Thread-46, started 704)>

task done <Thread(Thread-45, started 7840)>

task done <Thread(Thread-49, started 10924)>

task done <Thread(Thread-50, started 14836)>

------------all threads has finished <_MainThread(MainThread, started 10780)>

cost: 2.0096378326416016

此时可以在主线程中测子线程了

注意,为什么多线程会称之为多线程,因为会用join方法,有的哥们儿这样写

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done",threading.current_thread())#返回到当前线程的对象
start_time=time.time()
t_objs=[]
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.start()
   t_objs.append(t1)
   t1.join()#结束
print("------------all threads has finished",threading.current_thread())
print("cost:",time.time()-start_time)#主线程与子线程无关,所以不能在主线程中测子线程

他就变成了顺序的线程了,造成线程的阻塞了,应该竭力避免。

好了,我们再来看看守护(后台)线程的实例:

setDaemon   设置为后台线程或前台线程(默认)
                   如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止

                    如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

import threading
import time
def run(n):
   print("task",n)
   time.sleep(2)
   print("task done",threading.current_thread())#返回到当前线程的对象
start_time=time.time()
t_objs=[]
for i in range(50):
   t1=threading.Thread(target=run,args=("t-%s"%i,))
   t1.setDaemon(True)  # 把线程转变成守护线程,不管分线程,主进程结束了,直接结束,不等分线程
   t1.start()
   t_objs.append(t1)

# for t in t_objs:
#     t.join()#结束
print("------------all threads has finished",threading.current_thread())
print("cost:",time.time()-start_time)#主线程与子线程无关,所以不能在主线程中测子线程

这个案例比前面几个案例修改了什么了。没错,当主线程结束的时候,守护线程也会跟着结束

t1.setDaemon(True)  # 把线程转变成守护线程,不管分线程,主进程结束了,直接结束,不等分线程t1.start()

这个是固定格式,改变位置容易出错

以上是稍微简单的些的概念,接下来看第二部分

线程锁的引出:

一个进程可以启动多个进程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问一份数据,此时,如果有两个线程要修改同一份数据,会出现什么状况?

会导致数据出错,这里,我摘取前辈们的一部分解释,因为相比于2.7版本,3中做了许多改进,前辈们的问题已经不会出现,目测是python源代码中加了内置锁。

import  time
import  threading
 
def  addNum():
     global  num  #在每个线程中都获取这个全局变量
     print ( '--get num:' ,num )
     time.sleep( 1 )
     num   - = 1  #对此公共变量进行-1操作
 
num  =  100   #设定一个共享变量
thread_list  =  []
for  in  range ( 100 ):
     =  threading.Thread(target = addNum)
     t.start()
     thread_list.append(t)
 
for  in  thread_list:  #等待所有线程执行完毕
     t.join()
 
 
print ( 'final num:' , num )

正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。 

上面是我复制粘贴,原话,这里就不作解释了

我们来看一下怎样避免前辈们的情况

import time
import threading
def addNum():
   global num#在每个线程中都获得这个全局变量
   print('--get num:',num)
   time.sleep(1)
   lock.acquire()#修改数据前加锁
   num+=1
   lock.release()  # 修改后释放
   print('--get num', num)
num=0
thread_list=[]
lock = threading.Lock() #生成全局锁
for i in range(100):
   t=threading.Thread(target=addNum)
   t.start()
   thread_list.append(t)
for t in thread_list:
   t.join()
print('final num',num)

这是关于线程锁的一个解释,我们再来看一看关于线程锁的另一个解释

递归锁(对不起,各位,我的递归锁部分本来花了两个小时准备的,但是,因为我是编辑文章,所以没保存到草稿中,现在只能把重要部分讲给大家)

import threading, time

def run1():
   print("grab the first part data")
   lock.acquire()
   global num
   num += 1
   lock.release()
   return num
def run2():
   print("grab the second part data")
   lock.acquire()
   global num2
   num2 += 1
   lock.release()
   return num2
def run3():
   lock.acquire()
   res = run1()#调用run1() run1()是一个方法
   print('--------between run1 and run2-----')
   res2 = run2()#执行run2
   lock.release()
   print(res, res2)
num, num2 = 0, 0
lock = threading.RLock()#有一把锁
for i in range(10):
   t = threading.Thread(target=run3)
   t.start()
while threading.active_count() != 1:#主线程也是一个线程
   print(threading.active_count())
else:
   print('----all threads done---')

   print(num, num2)

信号量

所锁只能一个对数据进行更改,但信号量可以多个

import threading,time
def run(n):
   semaphore.acquire()
   time.sleep(1)
   global num
   num+=1
   print("run the thread:%s\n"%n)
   print(num)
   semaphore.release()
if __name__=='__main__':

   num=0
   semaphore=threading.BoundedSemaphore(5)#最多允许5个线程同时进行
   for i in range(22):
       t=threading.Thread(target=run,args=(i,))
       t.start()
while threading.active_count()!=1:
   pass
else:

   print('---all threads done----')

以下分享三个案例

先来一个简单的实例

import threading
import time


class MyThread(threading.Thread):
   def __init__(self, signal):
       threading.Thread.__init__(self)
       self.singal = signal

   def run(self):
       print("I am %s,I will sleep ..." % self.name)
       self.singal.wait()
       print("I am %s, I awake..." % self.name)


if __name__ == "__main__":
   singal = threading.Event()
   for t in range(0, 3):
       thread = MyThread(singal)
       thread.start()

   print("main thread sleep 3 seconds... ")
   time.sleep(3)

   singal.set()

关于红绿灯的

import time
import threading
event=threading.Event()
def lighter():
   count=0
   event.set()  # 变绿灯
   while True:
       if count>5 and count <10:#这一步是改成红灯
           event.clear()
           print("\033[41;1mred light is on....\033[0m")
       elif count>10:
           event.set()#变绿灯
           count=0
       else:
           print("\033[42;1mgreen light is on....\033[0m")
       time.sleep(1)
       count+=1
def car(name):
   while True:
       if event.is_set(): #代表绿灯
           time.sleep(0.1)
           print("[%s] running..."%name)

       else:
           print("[%s] sees red light , waiting...." %name)
           event.wait()
           print("\033[34;1m[%s] green light is on, start going...\033[0m" %name)

light = threading.Thread(target=lighter,)
light.start()
car1 = threading.Thread(target=car,args=("Tesla",))
car1.start()

#此处还有一个案例没完成,一个线程与多个线程之间的通信

未完待续,最近几天会推出线程与爬虫结合的实例与模板



      本文转自眉间雪 51CTO博客,原文链接:http://blog.51cto.com/13348847/1983306,如需转载请自行联系原作者





相关文章
|
10月前
|
Java 测试技术 API
【JUC】(1)带你重新认识进程与线程!!让你深层次了解线程运行的睡眠与打断!!
JUC是什么?你可以说它就是研究Java方面的并发过程。本篇是JUC专栏的第一章!带你了解并行与并发、线程与程序、线程的启动与休眠、打断和等待!全是干货!快快快!
1237 2
|
10月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
447 1
|
10月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
418 1
|
12月前
|
传感器 数据采集 监控
Python生成器与迭代器:从内存优化到协程调度的深度实践
简介:本文深入解析Python迭代器与生成器的原理及应用,涵盖内存优化技巧、底层协议实现、生成器通信机制及异步编程场景。通过实例讲解如何高效处理大文件、构建数据流水线,并对比不同迭代方式的性能特点,助你编写低内存、高效率的Python代码。
422 0
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
508 67
|
监控 编译器 Python
如何利用Python杀进程并保持驻留后台检测
本教程介绍如何使用Python编写进程监控与杀进程脚本,结合psutil库实现后台驻留、定时检测并强制终止指定进程。内容涵盖基础杀进程、多进程处理、自动退出机制、管理员权限启动及图形界面设计,并提供将脚本打包为exe的方法,适用于需持续清理顽固进程的场景。
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
调度 开发工具 Android开发
【HarmonyOS Next】鸿蒙应用进程和线程详解
进程的定义: 进程是系统进行资源分配的基本单位,是操作系统结构的基础。 在鸿蒙系统中,一个应用下会有三类进程:
492 0
|
SQL 监控 网络协议
YashanDB进程线程体系
YashanDB进程线程体系
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
450 1

热门文章

最新文章

推荐镜像

更多