小知识:格式化字符串的三种方法:
'....%s.' %i
"%"是Python风格的字符串格式化操作符;下面整理了一些符号及其含义
例子:注意:如果是%和浮点数要用两个%来表示%;如‘%.2f%%’
# 方式1:使用%运算符, %s表示任意字符,%d表示整数,%f表示浮点数
name = 'tom123456'
age = 18
height = 180.5
print('大家好,我叫:%2.4s 年龄:%d 身高:%.2f' % (name, age, height))
print('当前时间:%d年-%02d月-%d日' % (2019, 1, 24)) # %02d指月份为两位,不足两位则补0(针对int类型)
'''
%.2f 保留点数后的两位数(针对浮点型)
%2.4s 只能保留2-4位字符(针对字符串类型)
'''
format‘。{3}。。。{2}。。{1}’.format(a,b,c) ‘。{}。。。{}。。{}’.format(a,b,c)
例子:
(1)位置映射
name = 'tom123456'
age = 18
height = 180.5
print('大家好,我叫{0},年龄{1},身高{2:.2f}'.format(name, age, height)) # 按照指定索引位置
(2)关键字映射
name = 'tom123456'
age = 18
height = 180.5
print('大家好,我叫{name},年龄{age},身高{height}'.format(age=age, name=name, height=height)) # 按照指定名字
(3)元素访问
print("{0[0]}----{0[1]}".format(["python","java"]))
python----java
#添加多个列表的形式
print("{0[0]}---{0[1]}---{1[0]}---{1[1]}".format(["python","java"],["c++","R.language"]))
#利用元组的形式进行元素的访问
print("{0[0]}---{0[1]}".format((1,2)))
f嵌入式
name = 'tom123456'
age = 18
height = 180.5
# 方式3:在字符串前面添加一个f,使用{变量名}来嵌入变量
print(f'大家好,我叫{name},年龄{age},身高{height:.2f}')
分类 | 概念 |
---|---|
程序 | 一个应用可以当做一个程序,比如qq程序 |
进程 | 程序运行最小的资源分配单位。一个程序可以有多个进程 |
线程 | cpu最小的调度单位,必须依赖进程而存在。线程没有独立的资源,所有线程共享他的资源 |
详细介绍:
程序:是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程:是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
联系:一个程序至少有一个进程,一个进程至少有一个线程
单进程:只有一个进程
单线程:只有一个线程
问题:线程上下文的切换比进程上下文切换要快很多?
解答:
多线程:多线程是指一个程序包含多个并行的线程来完成不同的任务
多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多 核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
多线程的优点:可以提高cpu的利用率
代码:
import threading
t = threading.Thread(
target = 方法名,
args = (,) ## 方法的参数
)
t.start()
单线程:
import time
import random
#单线程爬虫
def download(fileName):
print(f"{fileName}文件开始下载")
time.sleep(random.random()*10)
print(f"{fileName}文件完成下载")
#单线程 默认主线程
if __name__ == '__main__':
for i in range(5):
## main下面调用函数属于单线程
# download(i)
多线程:
import time
import random
import threading
#多线程爬虫
def download(fileName):
print(f"{fileName}文件开始下载")
time.sleep(random.random()*10)
print(f"{fileName}文件完成下载")
#多线程 默认主线程
if __name__ == '__main__':
for i in range(5):
## main下面创建线程开启线程属于多线程
t = threading.Thread(target=download,args=(i,))
t.start()
多个函数多线程:
import random,time,threading
def sing():
for i in range(3):
print(f'{i}正在唱歌--------')
time.sleep(random.random())
def dance():
for i in range(3):
print(f'{i}正在跳舞------')
time.sleep(random.random())
if __name__ == '__main__':
#创建线程来启动这两个任务
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
#查看线程数量几个
while True:
length = len(threading.enumerate())
print(f'当前运行的线程数量:{length}')
time.sleep(random.random())
if length<=1:
break
运行结果:
#父类
class A:
def __init__(self):
print('父类init被触发')
def run(self):
print('父类的run被调用')
#子类
class B(A):
def run(self):
print('子类的run被调用')
#实例化过程
B()
线程类创建步骤:
线程类传参:必须在线程内的__init__()
调用父类的__init__()
方法(无论在__init__()方法
开头或者结尾调用都可以)
super().__init__()
也就是super(MyThread, self).__init__()
threading.Thread.__init__(self) ## self传当前类的cls
生存期:当我们启动一个线程到这个线程的任务方法执行完毕的过程就是这个线程的生命周期;即线程的整个状态。
查看当前进程下线程的数量
### 格式:threading.enumerate()
#查看线程数量几个
while True:
length = len(threading.enumerate())
print(f'当前运行的线程数量:{length}')
time.sleep(random.random())
if length<=1:
break
在线程中,我们可以通过MyThread(name= “downloadThread”)修改线程名称
线程的执行顺序是不固定的,原因主要是由线程状态决定的
线程的五种状态:
t=Mythread(i)或者使用t = threading.Thread(target=函数名)
。总结:多线程执行混乱 ! 无法确定先后顺序!!
# 多线程--全局变量--共享
from threading import Thread
import time
import random
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
time.sleep(random.random())
print('in work1,gum=%d' % g_num)
def work2():
global g_num
for i in range(3):
g_num += 1
time.sleep(random.random())
print('in work2,gum=%d' % g_num)
if __name__ == '__main__':
t1 = Thread(target=work1)
t2 = Thread(target=work2)
t1.start()
t2.start()
多个线程对公有变量处理时,容易造成数据的混乱,造成线程不安全问题
注意: 多个线程容易混乱,数据不安全
解决方法:下面提到的互斥锁
线程自己基本上不拥有系统资源,但是它可以与同属于一个进程的其它线程共享进程所拥有的全部资源。
一个程序至少有一个进程,一个进程至少有一个线程,线程的划分尺度小于进程(资源比进程少),使得多线程程序并发性更高。进程在执行过程中拥有独立的内存单元, 而多个线程共识这段儿内存空间。线程不能独立运行,必需依存进程。
线程和进程在使用上各有优势和缺点:线程的执行开销小,但不利于资源的管理和保存。进程正好相反。
(1)程序逻辑和控制方式复杂;
(2)所有线程可以直接共享内存和变量;
(3)线程方式消耗的总资源比进程方式好(资源缺少)。
(1)每个线程与主程序共用地址空间,受限于2GB地址空间;
(2)线程之间的同步和加锁控制比较麻烦;
(3)一个线程的崩溃可能影响到整个程序的稳定性;
(1)每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
(2)通过增加CPU,就可以容易扩充性能;
(3)每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大 。
(1)逻辑控制复杂,需要和主程序交互;
(2)需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。
在实际开发中,选择多线程和多进程应该从具体实际开发来进行选择。最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。
from threading import Thread
g_num = 0
def a1():
global g_num
for i in range(1000000):
# g_num += 1
b = g_num + 1
g_num = b
print("---test1---g_num=%d"%g_num)
def a2():
global g_num
for i in range(1000000):
a = g_num + 1
g_num = a
print("---test2---g_num=%d"%g_num)
if __name__ == '__main__':
p1 = Thread(target=a1)
p1.start()
p2 = Thread(target=a2)
p2.start()
#--------------------运行结果------------------------
---test2---g_num=1559989
---test1---g_num=1516811
Process finished with exit code 0
加join后
问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果达不到预期。这种现象我们称为“线程不安全”
互斥锁概念:
相当于互不影响的两个线程,一个走完再走另一个
创建互斥锁的步骤:
metux = threading.Lock()
if mutex.acpuire():
'''
对公有变量处理的代码
'''
mutex.release()#释放锁
完整案例代码:
import threading,time
#全局变量
g_num = 0
def w1():
global g_num
for i in range(10000000):
#上锁
mutexFlag = mutex.acquire(True)
if mutexFlag:
g_num+=1
#解锁
mutex.release()
print("test1---g_num=%d"%g_num)
def w2():
global g_num
for i in range(10000000):
# 上锁
mutexFlag = mutex.acquire(True)
if mutexFlag:
g_num+=1
# 解锁
mutex.release()
print("test2---g_num=%d" % g_num)
if __name__ == "__main__":
#创建锁
mutex = threading.Lock()
t1 = threading.Thread(target=w1)
t1.start()
t2 = threading.Thread(target=w2)
t2.start()
死锁第一种情况:当一个线程获取了锁之后,还未释放的前提下,试图获取另一把锁,此时会产生死锁。解决办法:判断:必须释放才能获取锁
死锁第二种情况:线程A获取锁1,线程B获取了锁2,线程A还未释放锁1继续想要获取锁2,线程B也未释放锁2,同时想要获取锁1。
银行家算法是避免死锁的一种重要方法。操作系统按照银行家制定的规则为线程分配资源,当线程首次申请资源时,要测试该线程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当线程在执行中继续申请资源时,先测试该线程已占用的资源数与本次申请的资源数之和是否超过了该线程对资源的最大需求量。若超过则拒绝分配资源,若没有超过则再测试系统现存的资源能否满足该进程尚需的最大资源量,若能满足则按当前的申请量分配资源,否则也要推迟分配。
银行家算法具体介绍:
每一个线程进入系统时,它必须声明在运行过程中,所需的每种资源类型最大数目,其数目不应超过系统所拥有每种资源总量,当线程请求一组资源系统必须确定有足够资源分配给该进程,若有在进一步计算这些资源分配给进程后,是否会使系统处于不安全状态,不会(即若能在分配资源时找到一个安全序列),则将资源分配给它,否则等待。
假定系统中有五个线程{P0,P1,P2,P3,P4}和三类资源{A,B,C},各类资源数量分别为10,5,7,在T0时刻分配资源情况如图:
Max:表示线程对每类资源的最大需求量;
Allocation:表示系统给线程已分配每类资源的数目;
Need:表示线程还需各类资源数目;
Available:表示系统当前剩下的资源。
从初始找出安全序列:
(1)首先系统剩下资源{3,3,2},查表可满足5个进程Need的进程有:P1(1,2,2)、P3(0,1,1),先给P1分配;
(2)P1分配以后执行完释放其所占资源后系统此时剩下资源有:Allocation+{3,3,2}={5,3,2};
(3)根据系统剩下资源查表可满足剩下4个进程Need的进程有P3{0,1,1}、P4{4,3,1},再给P3分配;
(4)P3分配以后执行完释放其所占资源后系统此时剩下资源有:Allocation+{5,3,2}={7,4,3};
(5)根据系统剩下资源查表可满足剩下3个进程Need的进程有P0{7,4,3}、P2{6,0,0}、P4{4,3,1},再给P4分配;
(6)P4分配以后执行完释放其所占资源后系统此时剩下资源有:Allocation+{7,4,3}={7,4,5};
(7)根据系统剩下资源查表可满足剩下2个进程Need的进程有P0{7,4,3}、P2{6,0,0},再给P2分配;
(8)P2分配以后执行完释放其所占资源后系统此时剩下资源有:Allocation+{7,4,5}={10,4,7};
(9)根据系统剩下资源查表可满足剩下1个进程Need的进程有P0{7,4,3},最后给P0分配;
(10)P0分配以后执行完释放其所占资源后系统此时剩下资源有:Allocation+{10,4,7}={10,5,7};
(11)所有进程按此序列{P1,P3,P4,P2,P0}可安全执行完毕,最后系统资源全部释放。(由以上也可知安全序列不唯一,但只要找出一个安全序列,说明此系统是安全的(找到安全序列可按此序列真正执行进程推进顺序,若没找到,则恢复初始状态,其并没有真正给进程分配资源,只是提前避免))
银行家算法在面试过程中需讲出原理,感兴趣的同学可用代码进行实现,这里就不展开了。
爬取网站的流程:
案例:腾讯招聘
普通类写法
import requests,json
class Tencent:
def __init__(self,url,headers):
self.url = url
self.headers = headers
self.parse()
def write_to_file(self,list_):
for item in list_:
with open("infos.txt","a+",encoding='utf-8') as fp:
fp.writelines(str(item)+'\n')
def parse_json(self,text):
infos = []
## 将json字符串解析变成python内置对象
json_dict = json.loads(text)
for data in json_dict["Data"]["Posts"]:
RecruitPostName = data['RecruitPostName']
CategoryName = data['CategoryName']
Responsibility = data['Responsibility']
LastUpdateTime = data['LastUpdateTime']
detail_url = data['PostURL']
item = {}
item["岗位名称"] = RecruitPostName
item["岗位类型"] = CategoryName
item["岗位要求"] = Responsibility
item["发布时间"] = LastUpdateTime
item['岗位详情页路由'] = detail_url
infos.append(item)
self.write_to_file(infos)
def parse(self):
for i in range(1,10):
params = {
'timestamp': '1572852398914',
'countryId': '',
'cityId': '',
'bgIds': '',
'productId': '',
'categoryId': '',
'parentCategoryId': '',
'attrId': '',
'keyword': '',
'pageIndex': str(i),
'pageSize': '10',
'language': 'zh-cn',
'area': 'cn'
}
response = requests.get(self.url,params=params,headers = self.headers)
# print(response.text)
self.parse_json(response.text)
if __name__ == '__main__':
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?'
headers = {'referer': 'https://careers.tencent.com/search.html',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
Tencent(base_url,headers)
多线程的第一种写法
import requests,json,threading
class Tencent:
def __init__(self,url,headers,params):
self.url = url
self.headers = headers
self.params = params
def write_to_file(self,list_):
for item in list_:
with open("infos.txt","a+",encoding='utf-8') as fp:
fp.writelines(str(item)+'\n')
def parse_json(self,text):
infos = []
## 将json字符串解析变成python内置对象
json_dict = json.loads(text)
for data in json_dict["Data"]["Posts"]:
RecruitPostName = data['RecruitPostName']
CategoryName = data['CategoryName']
Responsibility = data['Responsibility']
LastUpdateTime = data['LastUpdateTime']
detail_url = data['PostURL']
item = {}
item["岗位名称"] = RecruitPostName
item["岗位类型"] = CategoryName
item["岗位要求"] = Responsibility
item["发布时间"] = LastUpdateTime
item['岗位详情页路由'] = detail_url
infos.append(item)
self.write_to_file(infos)
def parse(self):
response = requests.get(self.url,params=self.params,headers = self.headers)
self.parse_json(response.text)
if __name__ == '__main__':
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?'
headers = {'referer': 'https://careers.tencent.com/search.html',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
t_list=[]
for i in range(1, 10):
params = {
'timestamp': '1572852398914',
'countryId': '',
'cityId': '',
'bgIds': '',
'productId': '',
'categoryId': '',
'parentCategoryId': '',
'attrId': '',
'keyword': '',
'pageIndex': str(i),
'pageSize': '10',
'language': 'zh-cn',
'area': 'cn'
}
tencent = Tencent(base_url,headers,params)
## 第一种方法开启线程(缺点:cpu卡顿,系统可能崩溃)
t = threading.Thread(target = tencent.parse)
t.start()
t_list.append(t)
## 将每个线程都调用join方法,保证测试运行时间是在每个线程运行执行完毕后的时间
for t in t_list:
t.join()
多线程的第二种写法
import requests,json,threading
from queue import Queue
class Tencent(threading.Thread):
def __init__(self,url,headers,name,q):
super().__init__()
self.url = url
self.headers = headers
self.name = name
self.q = q
def run(self):
self.parse()
def write_to_file(self,list_):
for item in list_:
with open("infos.txt","a+",encoding='utf-8') as fp:
fp.writelines(str(item)+'\n')
def parse_json(self,text):
infos = []
## 将json字符串解析变成python内置对象
json_dict = json.loads(text)
for data in json_dict["Data"]["Posts"]:
RecruitPostName = data['RecruitPostName']
CategoryName = data['CategoryName']
Responsibility = data['Responsibility']
LastUpdateTime = data['LastUpdateTime']
detail_url = data['PostURL']
item = {}
item["岗位名称"] = RecruitPostName
item["岗位类型"] = CategoryName
item["岗位要求"] = Responsibility
item["发布时间"] = LastUpdateTime
item['岗位详情页路由'] = detail_url
infos.append(item)
self.write_to_file(infos)
def parse(self):
while True:
if self.q.empty():
break
page = self.q.get()
print(f"++++++++++++++++++第{page}页++++++++是{self.name}线程运行的")
params = {
'timestamp': '1572852398914',
'countryId': '',
'cityId': '',
'bgIds': '',
'productId': '',
'categoryId': '',
'parentCategoryId': '',
'attrId': '',
'keyword': '',
'pageIndex': str(page),
'pageSize': '10',
'language': 'zh-cn',
'area': 'cn'
}
response = requests.get(self.url,params,headers = self.headers)
# print(response.text)
self.parse_json(response.text)
if __name__ == '__main__':
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?'
headers = {'referer': 'https://careers.tencent.com/search.html',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
## 1. 创建一个任务队列
queue = Queue()
## 2. 给队列添加任务;任务是每一页的页码
for page in range(1,10):
queue.put(page)
## 创建一个列表,里面放规定的线程数
thread_list = ["线程A","线程B","线程C","线程D"]
list = []
for thread_name in thread_list:
t = Tencent(base_url,headers,thread_name,queue)
t.start()
list.append(t)
for i in list:
i.join()