Python之路(第四十一篇)线程概念、线程背景、线程特点、threading模块、开启线程的方式
一、线程
之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么要有线程
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在三点上:
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
进程间的数据无法直接共享
线程的出现原因
正常情况下,我们在启动一个程序的时候。这个程序会先启动一个进程,启动之后这个进程会拉起来一个线程。这个线程再去处理事务。也就是说真正干活的是线程,进程这玩意只负责向系统要内存,要资源但是进程自己是不干活的。默认情况下只有一个进程只会拉起来一个线程。
多线程顾名思义,就是同样在一个进程的情况同时拉起来多个线程。上面说了,真正干活的是线程。进程与线程的关系就像是工厂和工人的关系。那么现在工厂还是一个,但是干活的工人多了。那么效率自然就提高了。因为只有一个进程,所以多线程在提高效率的同时,并没有向系统伸手要更多的内存资源。因此使用起来性价比还是很高的。但是多线程虽然不更多的消耗内存,但是每个线程却需要CPU的的参与。
相当于工厂虽然厂房就一间,可以有很多的工人干活。但是这些工人怎么干活还得靠厂长来指挥。工人太多了,厂长忙不过来安排一样效率不高。所以工人(线程)的数量最好还是在厂长(cpu)的能力(内核数)范围之内比较好。
线程出现的背景
60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
每一个进程中至少有一个线程。
线程特点
1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述,
2)独立调度和分派的基本单位。 在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3)共享进程资源。 线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
4)可并发执行。 在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
线程和进程的关系
根据上图可以看出,进程包含线程。也就是说默认情况下 一个进程肯定会有一个线程的,这个线程叫主线程。
多线程也就是在一个进程里面开出多个线程。多进程里面也可以包含多线程。
多进程之间是不可以直接通讯的。但是由于多线程是被同一个进程包裹,故多线程中资源共享即可以直接通讯。
进程本身不能够执行
进程和线程不能比较谁快谁慢,两个没有可比性,进程是资源的集合,线程是真正执行任务的,进程要执行任务也要通过线程
启动一个线程比启动一个进程快,线程上下文切换比进程上下文切换要快得多
python线程模块的选择
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。 一般避免使用thread模块,使用更高级别的threading模块,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突。
二、threading模块
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性
开启线程的两种方式
1、方式一
from threading import Thread
import time
def func(n):
time.sleep(2)
print("线程是%s"%n)
global g
g = 0
print(g)
if __name__ == '__main__':
g = 100
t_l = []
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
t_l.append(t) for t in t_l:
t.join()
print("主线程G",g)
2、方式二
import threading
import time def func(n):
time.sleep(2)
print("线程是%s" % n)
print('子线程的ID号A', threading.current_thread().ident)
global g
g = 0
print('子线程中的g', g) class Mythread(threading.Thread): def __init__(self, arg, *args, **kwargs):
super().__init__(*args, **kwargs)
self.arg = arg def start(self):
print('start-----')
super().start() # 调用父类的start()和run()方法 def run(self):
print("类中的子线程", self.arg)
super().run()
print('子线程的ID号B',threading.current_thread().ident) if __name__ == '__main__':
g = 100
t1 = Mythread('hello', target=func, name="MyThread", args=('nick',))
# 第一个参数是用在Mythread类中的,后面的3个参数用在创建的func子线程中,args必须是可迭代的
# 这里的func也可以直接写在Mythread中的run()里,这时这里的run()不用再继承父类的run()
t1.start()
# t1.run()
t1.join()
print('主线程中的g', g)
print('主线程的ID号---', threading.current_thread().ident)
简化版:
import time
from threading import Thread
class MyTread(Thread):
def __init__(self,arg):
super().__init__()
self.arg = arg
def run(self):
time.sleep(1)
print(self.arg)
print('直接在这里写子进程的代码') t = MyTread('hello')
t.start()
多线程与多进程区别
1、运行方式不同:
进程不能单独执行,它只是资源的集合。
进程要操作CPU,必须要先创建一个线程。
所有在同一个进程里的线程,是同享同一块进程所占的内存空间。
2、关系
进程中第一个线程是主线程,主线程可以创建其他线程;其他线程也可以创建线程;线程之间是平等的。
进程有父进程和子进程,独立的内存空间,唯一的标识符:pid。
3、速度
启动线程比启动进程快。
运行线程和运行进程速度上是一样的,没有可比性。
线程共享内存空间,进程的内存是独立的。
4、创建
父进程生成子进程,相当于复制一份内存空间,进程之间不能直接访问
创建新线程很简单,创建新进程需要对父进程进行一次复制。
一个线程可以控制和操作同级线程里的其他线程,但是进程只能操作子进程。
5、交互
同一个进程里的线程之间可以直接访问。
两个进程想通信必须通过一个中间代理来实现。
测试下进程和线程谁开启的速度快
from multiprocessing import Process
from threading import Thread
import time
def func(n):
n + 1
if __name__ == "__main__":
start_t = time.time()
t_li = []
for i in range(100):
t = Thread(target=func, args=(i,))
t.start()
t_li.append(t)
for t in t_li: t.join()
druing_time1 = time.time() - start_t
start_p = time.time()
p_li = []
for i in range(100):
p = Process(target=func, args=(i,))
p.start()
p_li.append(p)
for p in p_li: p.join()
druing_time2 = time.time() - start_t
print(druing_time1,druing_time2)
分析:根据执行结果显示:开启线程的速度比进程的速度快上百倍以上。
看下多线程和多进程的PID
from threading import Thread
from multiprocessing import Process
import os
def work():
print('函数内hello',os.getpid())
if __name__ == '__main__':
#part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
t1=Thread(target=work)
t2=Thread(target=work)
t1.start()
t2.start()
print('主线程/主进程pid',os.getpid())
#part2:开多个进程,每个进程都有不同的pid
p1=Process(target=work)
p2=Process(target=work)
p1.start()
p2.start()
print('主线程/主进程pid',os.getpid())
测试下多线程内共享数据的情况
from threading import Thread
from multiprocessing import Process
import os
def work():
global n
n=0
if __name__ == '__main__':
# 多进程的情况
# n=100
# p=Process(target=work)
# p.start()
# p.join()
# print('主',n)
#毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
#
#
# 多线程的情况
n=1
t=Thread(target=work)
t.start()
t.join()
print('主',n)
#查看结果为0,因为同一进程内的线程之间共享进程内的数据,
# 这表示线程内数据是共享的
多线程下的socket客户端和服务端
服务端
import socket
from threading import Thread
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8081))
server.listen(5)
buffer_size = 1024
def chat(conn,addr):
res = conn.recv(buffer_size)
print("addr",addr,res.decode("utf-8"))
msg = input("请输入:").strip()
conn.send(msg.encode("utf-8"))
def func():
while True:
print("服务器开始运行了")
conn,addr = server.accept()
res = Thread(target=chat,args=(conn,addr)).start()
func()
客户端
import socket
buffer_size = 1024
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8081))
while True:
msg = input("请输入").strip()
client.send(msg.encode("utf-8"))
res = client.recv(buffer_size)
print(res.decode("utf-8"))
分析:这里可测试同时开启多个客户端与服务端聊一次天
参考链接
[1]https://blog.csdn.net/u013421629/article/details/79208227
[2]https://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
Python之路(第四十一篇)线程概念、线程背景、线程特点、threading模块、开启线程的方式的更多相关文章
- Python使用Threading模块创建线程
使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法: #!/usr/bin/python # -*- coding: UTF-8 ...
- 使用threading模块创建线程
#_author:来童星#date:2019/12/17#使用threading模块创建线程import threading,timedef process(): for i in range(3): ...
- Python之网路编程利用threading模块开线程
一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 1 1.创建线程的开销比创建进程的开销 ...
- Python——threading模块(线程)
一.threading模块的对象 Thread:表示一个执行线程的对象 Lock:锁 Rlock:可重入锁对象 Condition:条件变量对象,使得一个线程等待另一个线程满足特定的“条件” Even ...
- 利用threading模块开线程
一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 1.创建线程的开销比创建进程的开销小, ...
- threading模块创建线程
什么是线程 (thread) 线程也是一种多任务编程方式,可以使用计算机的多核资源.线程被称为轻量级的进程. 线程特征 *线程计算机多核分配的最小单位 *一个进程可以包含多个线程 *线程也是一个运行的 ...
- python之路基础篇
基础篇 1.Python基础之初识python 2.Python数据类型之字符串 3.Python数据类型之列表 4.Python数据类型之元祖 5.Python数据类型之字典 6.Python Se ...
- python之路——基础篇(2)模块
模块:os.sys.time.logging.json/pickle.hashlib.random.re 模块分为三种: 自定义模块 第三方模块 内置模块 自定义模块 1.定义模块 将一系列功能函数或 ...
- python之路第一篇
一.python环境的搭建 1.window下环境的搭建 (1).在 https://www.python.org/downloads/ 下载自己系统所需要的python版本 (2).安装python ...
随机推荐
- org.apache.hadoop.util.Shell demo/例子
package cn.shell; import java.io.IOException; import org.apache.hadoop.util.Shell; public class Shel ...
- JavaScript基础09——事件驱动
1.事件驱动 js控制页面的行为是由事件驱动的. 什么是事件?(怎么发生的) 事件就是js侦测到用户的操作或是页面上的一些行为 事件源(发生在谁身上) 引 ...
- Debian9 安装软件汇总
dpkg dpkg -i 安装本地安装包 echo 'pkgname newstat' | dpkg --set-selections 修改软件包安装状态 newstat install,deinst ...
- [BZOJ1015/JSOI2008]星球大战
// 此博文为迁移而来,写于2015年7月16日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w6le.html 1.题 ...
- Java 抽象类 抽象方法 接口
#抽象类 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类除了不能实例化 ...
- Guava---缓存之LRU算法
随笔 - 169 文章 - 0 评论 - 292 GuavaCache学习笔记一:自定义LRU算法的缓存实现 前言 今天在看GuavaCache缓存相关的源码,这里想到先自己手动实现一个LRU ...
- 2018-2019-2 网络对抗技术 20165230 Exp9 :Web安全基础
目录 实验目的 实验内容 Webgoat前期准备 出现的问题 (一)SQL注入攻击 命令注入:Command Injection 数字型注入:Numeric SQL Injection 日志欺骗:Lo ...
- 2019年上海市大学生网络安全大赛两道misc WriteUp
2019年全国大学生网络安全邀请赛暨第五届上海市大学生网络安全大赛 做出了两道Misc== 签到 题干 解题过程 题干提示一直注册成功,如果注册失败也许会出现flag. 下载下来是包含010edito ...
- 异步IRP的教训(已附DUMP)
[教训]异步IRP中,IoSetCompletionRoutine()要在IoCallDriver()的前面,不然底层驱动完成了读写之后,找不到完成例程,会导致出错.看似简单,不小心却可能带来大麻烦. ...
- Maven私服配置Setting和Pom文件
上一遍博客已经在linux服务器上,搭建好nexus私服了 现在就需要配置setting.xml和pom.xml来使nexus作为maven的私服.setting.xml文件在conf下面,pom.x ...