python進(jìn)階
數(shù)據(jù)庫(kù)
概念
概念:
高效的存儲(chǔ)和處理數(shù)據(jù)的介質(zhì)(主要分為磁盤(pán)和內(nèi)存兩種)
按照一定的數(shù)據(jù)結(jié)構(gòu)來(lái)組織、存儲(chǔ)和管理數(shù)據(jù)的倉(cāng)庫(kù)
分類(lèi):
關(guān)系型數(shù)據(jù)庫(kù)(SQL)
安全篙骡,其將數(shù)據(jù)保存到磁盤(pán)之中
Oracle稽坤、MySQL 和 SQL Server 等。
非關(guān)系型數(shù)據(jù)庫(kù)(NoSQL医增,Not Only SQL)
存儲(chǔ)數(shù)據(jù)的效率比較高,但儲(chǔ)存方式比較靈活慎皱,不太安全
Memcached、MongoDB 和 Redis 等叶骨。
區(qū)別
關(guān)系型數(shù)據(jù)庫(kù)(SQL)
數(shù)據(jù)存儲(chǔ)在特定結(jié)構(gòu)的表中
Oracle茫多、MySQL 和 SQL Server 等。
非關(guān)系型數(shù)據(jù)庫(kù)(NoSQL忽刽,Not Only SQL)
存儲(chǔ)方式比較靈活天揖,數(shù)據(jù)以對(duì)象的的形式存儲(chǔ)在數(shù)據(jù)庫(kù)中。
MySQL
mysql簡(jiǎn)介
mysql是最流行的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)之一跪帝,由瑞典MySQL AB公司開(kāi)發(fā)今膊,目前屬于Oracle公司。 MySQL是一種關(guān)聯(lián)數(shù)據(jù)庫(kù)管理系統(tǒng)伞剑,關(guān)聯(lián)數(shù)據(jù)庫(kù)將數(shù)據(jù)保存在不同的表中斑唬,而不是將所有數(shù)據(jù)放在一個(gè)大倉(cāng)庫(kù)內(nèi),這樣就增加了速度并提高了靈活性嫂伞。
(開(kāi)源若锁,免費(fèi))
關(guān)系型數(shù)據(jù)庫(kù):采用關(guān)系模型來(lái)組織數(shù)據(jù)的數(shù)據(jù)庫(kù)
關(guān)系:一張二維表挟秤,每個(gè)關(guān)系都有一個(gè)關(guān)系名宪萄,就是表名
模型:行和列(二維),具體指字段跟字段信息
庫(kù)級(jí)和表級(jí)操作
#進(jìn)入與退出 數(shù)據(jù)庫(kù)
#進(jìn)入 mysql –uusername -ppassword
#退出 mysql> exit
庫(kù)級(jí)操作語(yǔ)句
顯示所有庫(kù): show databases;
創(chuàng)建庫(kù): create database [if not exists] db_name;
刪除庫(kù): drop database [if exists] db_name;
切換當(dāng)前數(shù)據(jù)庫(kù): use db_name;
# 注意:
1.重復(fù)創(chuàng)建會(huì)報(bào)錯(cuò)荧琼,所以可以加上if not exists
2.如果不知道數(shù)據(jù)庫(kù)是否存在讨便,記得加if exists
表級(jí)操作語(yǔ)句
顯示所有表: show tables;
創(chuàng)建表: create table tbl_name(create_definition,...);
顯示創(chuàng)建表的信息:show create table tb_name;
刪除表: drop table tb_name;
注意事項(xiàng)
語(yǔ)句結(jié)束符: 每個(gè)語(yǔ)句都以 ; 或 \G 結(jié)束
大小寫(xiě): 不嚴(yán)格區(qū)分大小寫(xiě), 默認(rèn)大寫(xiě)為程序代碼育谬,小寫(xiě)為程序員寫(xiě)的代碼
類(lèi)型: 強(qiáng)制數(shù)據(jù)類(lèi)型含蓉、任何數(shù)據(jù)都有自己的類(lèi)型
逗號(hào): 創(chuàng)建表最后一列不需要逗號(hào)
表中數(shù)據(jù)的增频敛、刪、查馅扣、改
# CRUD操作
C:CREATE/INSERT #創(chuàng)建/插入
R:READ/SELECT #讀取/查詢
U:UPDATE #更新
D:DELETE #刪除
創(chuàng)建/插入
指定列插入: INSERT INTO tb_name(col_names) VALUES (col_values);
全列插入: INSERT INTO tb_name VALUES (all_values);
多行插入: INSERT INTO tb_name(col_names) VALUES (value_1), (value_2), …;
# 注意:區(qū)別
指定列插入:插入某列某值,單行操作
全列插入:需要給所有列賦值,單行操作
多行插入:插入多行數(shù)據(jù)
查詢
指定列查詢:SELECT col_names FROM tb_name;
全列查詢: SELECT * FROM tb_name;
帶條件的查詢: SELECT col_names FROM tb_name WHERE conditions;
修改
修改表中所有數(shù)據(jù):UPDATE tb_name SET field_1=value_1
修改表中指定的數(shù)據(jù): UPDATE tb_name SET field_1=value_1 WHERE conditions;
注意:一定要寫(xiě)where條件斟赚,不然會(huì)修改表中全部數(shù)據(jù)
刪除
刪除表中所有數(shù)據(jù):DELETE FROM tb_name;
刪除表中滿足條件的數(shù)據(jù): DELETE FROM tb_name WHERE conditions;
# 注意:一定要寫(xiě)where條件,不然會(huì)刪除表中全部數(shù)據(jù)
mysql常用數(shù)據(jù)類(lèi)型
create table tb2(
id INT,
name VARCHAR(20), #指定長(zhǎng)度差油,最多65535個(gè)字符汁展。 變長(zhǎng)字符串
sex CHAR(4), #指定長(zhǎng)度,最多255個(gè)字符厌殉。 定長(zhǎng)字符串
price DOUBLE(4,2), #雙精度浮點(diǎn)型,m總個(gè)數(shù)侈咕,d小數(shù)位
detail text, #可變長(zhǎng)度公罕,最多65535個(gè)字符
dates DATETIME, #日期時(shí)間類(lèi)型 YYYY-MM-DD HH:MM:SS
ping ENUM('好評(píng)','差評(píng)’) #枚舉, 在給出的value中選擇
);
insert into tb2 value( 1,'褲子','男',20.0,'這條褲子超級(jí)好RBゾ臁!',now(),'好評(píng)');
表結(jié)構(gòu)修改
修改表名: ALTER TABLE tb_name RENAME TO new_students;
修改字段名: ALTER TABLE tb_name CHANGE name new_name CHAR(10);
修改字段類(lèi)型: ALTER TABLE tb_name MODIFY field_name CHAR(10);
表描述: DESC tb_name;
添加列: ALTER TABLE tb_name ADD [COLUMN] name varchar(10);
添加到第一列: ALTER TABLE tb_name ADD [COLUMN] name first;
添加到指定列后: ALTER TABLE tb_name ADD name varchar(10) after field_name;
刪除列: ALTER TABLE tb_name DROP [COLUMN] name;
約束條件
思考
問(wèn)題一: 如果插入一條記錄的時(shí)候熊尉, 某列沒(méi)有插入值罐柳,會(huì)是什么 ?
問(wèn)題二: 能否確保某列的值絕對(duì)不為空 狰住?
問(wèn)題三: 能否確保某列的值不重復(fù) 张吉?
問(wèn)題四: 能否確保某列的值必須參照另一列 ?
約束條件
約束是一種限制催植,通過(guò)對(duì)表中的數(shù)據(jù)做出限制肮蛹,來(lái)確保表中數(shù)據(jù)的完整性,唯一性创南。
默認(rèn)約束 default
default :初始值設(shè)置伦忠,插入記錄時(shí),如果沒(méi)有明確為字段賦值稿辙,則自動(dòng)賦予默認(rèn)值昆码。
給一個(gè)字段添加默認(rèn)值 例子:
create table tb(
id int,
name varchar(20),
age int default 18
);
#創(chuàng)建了表后添加
mysql> alter table tb modify age int default 20;
#刪除default
mysql> alter table tb modify age int;
not null 非空約束
注意:
空字符不等于null;
限制一個(gè)字段的值不能為空,insert的時(shí)候必須添加改字段
例子:
CREATE TABLE tb1(
id int,
name varchar(20) NOT NOLL
);
#手動(dòng),添加非空約束
mysql> alter table tb1 modify id int not null;
# 取消非空約束
mysql> alter table tb1 modify id int ;
unique key 唯一約束
確保字段中的值的唯一
例子:
CREATE TABLE tb2(
id int UNIQUE KEY,
name varchar(20)
);
#添加唯一約束
mysql> alter table tb2
->modify name varchar(20) unique
->;
#刪除唯一約束
mysql> alter table tb2
-> drop key name;
主鍵約束 primary key
主鍵的作用: 可以唯一標(biāo)識(shí) 一條數(shù)據(jù)赋咽,每張表里面只能有一個(gè)主鍵,旧噪。
主鍵特性: 非空且唯一。當(dāng)表里沒(méi)有主鍵的時(shí)冬耿,第一個(gè)出現(xiàn)的非空且為唯一的列舌菜,被當(dāng)成主鍵。
例子:
create table tb4(
id int primary key,
name varchar(20) not null
);
唯一標(biāo)識(shí) 一條數(shù)據(jù)
主鍵 = 非空 + 唯一
#刪除主鍵約束
mysql -> alter table tb4
-> drop primary key;
#添加主鍵約束
mysql> alter table tb3
-> add primary key(id);
自增長(zhǎng) auto_increment
auto_increment :自動(dòng)編號(hào)亦镶,一般與主鍵組合使用日月。一個(gè)表里面只有一個(gè)自增,默認(rèn)情況下缤骨,起始值為1爱咬,每次的增量為1。
例子:
create table tb3(
id int primary key auto_increment,
name varchar(20)
)auto_increment =100;
AUTO_INCREMENT 一般用
在主鍵上
#刪除自動(dòng)增長(zhǎng)
mysql> alter table tb3 modify id int;
#增加自動(dòng)增長(zhǎng)auto_increment
mysql> alter table tb3
-> modify id int auto_increment;
外鍵約束 foreign key
外鍵約束 :保持?jǐn)?shù)據(jù)一致性绊起,完整性實(shí)現(xiàn)一對(duì)多關(guān)系精拟。
外鍵必須關(guān)聯(lián)到鍵上面去,一般情況是虱歪,關(guān)聯(lián)到另一張表的主鍵
#增加外鍵
mysql> alter table b
-> add constraint AB_foreign foreign key(b_id) references a(a_id);
#刪除外鍵
alter table b drop foreign key AB_foregin;
B表中的b_id 字段蜂绎,只能添加 a_id中 已有的數(shù)據(jù)。
A表中a_id 被參照的數(shù)據(jù),不能被修改和刪除笋鄙,
要先刪除b_id中的數(shù)據(jù)师枣,才能刪除a_id中的數(shù)據(jù)
子查詢
子查詢 問(wèn)題引入
問(wèn)題一:如何找到“張三”的成績(jī) ?
問(wèn)題二:能否將一個(gè)查詢的結(jié)果留下來(lái)用于下一次查詢 萧落?
案例:
找到張才的成績(jī) => 先找到張三的學(xué)號(hào)践美,再用這個(gè)學(xué)號(hào)去篩選成績(jī)表就能找到 !
SELECT number FROM students WHERE name=‘張三’;
SELECT student_number,subjects,grade FROM grades
WHERE student_number=201804003;
嵌套查詢
如果把查詢結(jié)果當(dāng)作條件呢 找岖?
將學(xué)生表的查詢結(jié)果嵌套進(jìn)成績(jī)表中陨倡!
SELECT grade FROM grades
WHERE student_number = (SELECT number FROM students WHERE name='張三');
連接查詢
交叉連接
交叉連接又名笛卡爾連接它列出了所有情況其中一定有你需要的組合
內(nèi)連接
左表 [inner] join 右表 on 左表.字段 = 右表.字段;
on 后面是篩選條件
條件字段就是代表相同的業(yè)務(wù)含義(如my_student.c_id和my_class.id)
select name, subject_number as number, grade from
students join grades on
students.number = grades.student_number;
事務(wù)/編碼
原子性
所做操作要么完成,要不完成,不會(huì)執(zhí)行過(guò)程終止
一致性
執(zhí)行操作前和操作后,數(shù)據(jù)完整性沒(méi)有破壞
隔離性
同時(shí)讀寫(xiě)修改同一個(gè)數(shù)據(jù),保證數(shù)據(jù)的一致性
持久性
對(duì)數(shù)據(jù)的修改是持久的,即使系統(tǒng)破壞數(shù)據(jù)也不會(huì)消失
編碼問(wèn)題
創(chuàng)建數(shù)據(jù)庫(kù)自定義編碼
CREATET DATABASE tb_name CHARACTER SET gbk;
創(chuàng)建數(shù)據(jù)表自定義編碼
CREATE TABLE tb_name (
id INT
) CHARSET utf8;
編輯配置文件:
vim /etc/mysql/mysql.conf.d/mysqld.cnf
重啟數(shù)據(jù)庫(kù): sudo service mysql restart
遠(yuǎn)程連接
配置文件:
vim /etc/mysql/mysql.conf.d/mysqld.cnf
第一步:修改監(jiān)聽(tīng)I(yíng)P
bind 0.0.0.0
第二步:給用戶添加遠(yuǎn)程訪問(wèn)權(quán)限
update user set host ='%' where user='root';
第三步:重啟數(shù)據(jù)庫(kù)
sudo service mysql restart
用戶權(quán)限
Root賬號(hào)權(quán)限太大,一般只在管理數(shù)據(jù)庫(kù)的時(shí)候 ,一般項(xiàng)目都是創(chuàng)建一個(gè)用戶去操作
創(chuàng)建用戶:CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
用戶授權(quán):grant 權(quán)限 on 數(shù)據(jù)庫(kù).* to 用戶名@登錄主機(jī) identified by "密碼";
取消用戶授權(quán): revoke privilege ON databasename.tablename from 'username'@'%';
篩選條件
比較運(yùn)算符
等于: = ( 注意许布!不是 == ) | 大于等于: >= | IS NULL |
---|---|---|
不等于: != 或 <> | 小于: < | IS NOT NULL |
大于: > | 小于等于: <= |
NULL 判斷
INSERT INTO students VALUES ('201804011', NULL, NULL, NULL, NULL);
邏輯運(yùn)算符
與(且): AND
或: OR
非(不是): NOT
范圍查詢
連續(xù)范圍: BETWEEN a AND b (a <= value <= b)
間隔返回: IN
模糊查詢
任意多個(gè)字符: % ( like ‘%’ )
任意一個(gè)字符: _
排序
SELECT columns
FROM tb_name
ORDER BY ord_col_1 [asc/desc];
正序: asc(默認(rèn))
倒序: desc
去重
SELECT DISTINCT columns
FROM tb_name;
聚合分組
問(wèn)題引入
問(wèn)題一: 僅從文字上看兴革,你認(rèn)為聚合是什么意思 ?(把多個(gè)值聚在一起)
問(wèn)題二: 聚合通常是為了什么目的 蜜唾?(統(tǒng)計(jì)信息)
問(wèn)題三: 分組對(duì)于聚合而言意味著什么 帖旨?(分組統(tǒng)計(jì) group by)
問(wèn)題四: 能否對(duì)篩選聚合值 ?(可以灵妨,having)
常用聚合函數(shù)
統(tǒng)計(jì)個(gè)數(shù): COUNT(column)
最大值: MAX(column)
最小值: MIN(column)
求和: SUM(column)
平均值: AVG(column)
列出字段全部值:GROUP_CONCAT(column)
分組查詢
SELECT group_column, aggregations
FROM tb_name
GROUP BY group_column;
在分組的情況下解阅,只應(yīng)該出現(xiàn)分組列和聚合列
其他的列沒(méi)有意義 , 會(huì)報(bào)錯(cuò)!
聚合篩選
SELECT group_column, aggregations
FROM tb_name
GROUP BY group_column
HAVING conditions;
加HAVING 條件表達(dá)式,可以限制輸出的結(jié)果
WHERE 不能使用別名泌霍, having可以使用別名货抄!
WHERE 不能篩選聚合函數(shù)述召!
總結(jié)
假如說(shuō), 一個(gè)查詢中同時(shí)包含了 別名, 聚合函數(shù),WHERE蟹地、HAVING 那么它們的執(zhí)行順序是:
先是執(zhí)行: WHERE
然后執(zhí)行: 聚合函數(shù)和別名
最后執(zhí)行: HAVING
限制與分頁(yè)
問(wèn)題引入
問(wèn)題一: 如果一次性不需要那么多數(shù)據(jù)积暖,該如何做 ?(對(duì)結(jié)果做出限制)
問(wèn)題二: 能否從指定位置開(kāi)始取 怪与?(可以)
問(wèn)題三: 分頁(yè)是如何做的 夺刑?(limit)
限制結(jié)果個(gè)數(shù)
SELECT columns FROM tb_name LIMIT count;
SELECT columns FROM tb_name LIMIT start, count;
分頁(yè)
SELECT columns FROM tb_name LIMIT count;
SELECT columns FROM tb_name LIMIT start, count;
程序中分頁(yè)
SELECT columns FROM tb_name LIMIT (n-1)*m, m
在python中操作mysql
統(tǒng)一的接口標(biāo)準(zhǔn)DB-API
連接接口: pymysql.connect(**dbconfig)
關(guān)閉接口: cnn.close()
游標(biāo)接口: cnn.cursor()
關(guān)閉游標(biāo): cur.close()
執(zhí)行接口: cur.execute(sql)
結(jié)果集接口: cur.fetchall()
#連接是不能操作數(shù)據(jù)庫(kù)的, 需要用連接生成游標(biāo)來(lái)從操作
程序連接數(shù)據(jù)庫(kù)
db_config = {
'user': 'root',
'password': 'qwe123',
'db': 'python3',
'charset': 'utf8', # 不是utf-8
}
# conn = pymysql.connect(**db_config)
別忽略了這些點(diǎn)
注意點(diǎn)一: 和文件一樣,別忘了關(guān)閉 游標(biāo)與連接
注意點(diǎn)二: 在pymysql中執(zhí)行SQL不需要加 ;
注意點(diǎn)三: execute 執(zhí)行完后不是直接得到結(jié)果集而需要你主動(dòng)去獲取
Redis
什么是NoSQL分别?
NoSQL:一類(lèi)新出現(xiàn)的數(shù)據(jù)庫(kù)(not only sql)遍愿,它的特點(diǎn):
不支持SQL語(yǔ)法
存儲(chǔ)結(jié)構(gòu)跟傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)中的那種關(guān)系表完全不同,nosql中存儲(chǔ)的數(shù)據(jù)都是Key Value形式
NoSQL的世界中沒(méi)有一種通用的語(yǔ)言耘斩,每種nosql數(shù)據(jù)庫(kù)都有自己的api和語(yǔ)法沼填,以及擅長(zhǎng)的業(yè)務(wù)場(chǎng)景
NoSQL中的產(chǎn)品種類(lèi)相當(dāng)多:
Mongodb
Redis
Hbase hadoop
Cassandra hadoop
什么是redis
Redis簡(jiǎn)介:
Redis是一個(gè)開(kāi)源免費(fèi)、可基于內(nèi)存亦可持久化的鍵值對(duì)存儲(chǔ)數(shù)據(jù)可以 括授,使用 C語(yǔ)言編寫(xiě),并提供多種語(yǔ)言的API坞笙。從2010年3月15日起,Redis的開(kāi)發(fā)工作由VMware主持荚虚。從2013年5月開(kāi)始薛夜,Redis的開(kāi)發(fā)由Pivotal贊助。
Redis是 NoSQL技術(shù)陣營(yíng)中的一員版述,它通過(guò)多種鍵值數(shù)據(jù)類(lèi)型來(lái)適應(yīng)不同場(chǎng)景下的存儲(chǔ)需求却邓,借助一些高層級(jí)的接口使用其可以勝任,如緩存院水、隊(duì)列系統(tǒng)的不同角色
Redis特性:
Redis支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保存在磁盤(pán)中简十,重啟的時(shí)候可以再次加載進(jìn)行使用檬某。
Redis不僅僅支持簡(jiǎn)單的key-value類(lèi)型的數(shù)據(jù),同時(shí)還提供list螟蝙,set恢恼,zset,hash等數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)胰默。
Redis支持?jǐn)?shù)據(jù)的備份场斑,即master-slave模式的數(shù)據(jù)備份。
如何使用Redis服務(wù)端和客戶端命令牵署?
服務(wù)器端:
服務(wù)器端的命令為redis-server
可以使?help查看幫助?檔
redis-server –help
啟動(dòng)
sudo service redis start
停?
sudo service redis stop
重啟 sudo service redis restart
客戶端:
客戶端的命令為redis-cli
可以使?help查看幫助?檔
redis-cli --help
連接redis
redis-cli
切換數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)沒(méi)有名稱(chēng)漏隐,默認(rèn)有16個(gè),通過(guò)0-15來(lái)標(biāo)識(shí)奴迅,連接redis默認(rèn)選擇第一個(gè)數(shù)據(jù)庫(kù)
select n
常用數(shù)據(jù)類(lèi)型與命令
Reids的數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu):
redis是key-value的數(shù)據(jù)結(jié)構(gòu)青责,每條數(shù)據(jù)都是?個(gè)鍵值對(duì)
鍵的類(lèi)型是字符串
注意:鍵不能重復(fù)
值的類(lèi)型分為五種:
字符串string
哈希hash
列表list
集合set
有序集合zset
基本數(shù)據(jù)類(lèi)型
String 字符串
Hash 哈希
List 列表
Set 集合
List 有序集合
Sting類(lèi)型
string是redis最基本的類(lèi)型,一個(gè)key對(duì)應(yīng)一個(gè)value。
SET key value 設(shè)置
GET key 獲取
APPEND key value 追加
SET key value EX seconds 指定過(guò)期時(shí)間
全局Key操作
KEYS * | 查看查看所有的key |
---|---|
DEL key | 刪除 |
EXISTS key | 查看key是否存在 |
RENAME key newkey | 改名 |
EXPIRE key seconds | 設(shè)置超時(shí)時(shí)間 |
TTL key | 返回生存時(shí)間 |
PERSIST key | 刪除生存時(shí)間 |
List類(lèi)型
List類(lèi)型是按照插入順序排序的字符串鏈表脖隶。在插入時(shí)扁耐,如果該鍵并不存在,Redis將為該鍵創(chuàng)建一個(gè)产阱。
LPUSH key value [value…] | 左添加 / rpush |
---|---|
LINDEX key index | 查看list中某一個(gè)元素 |
lRANGE key start stop | 查看多個(gè)元素 |
LLEN key | 查看list元素個(gè)數(shù) |
LSET key index value | 修改 |
LPOP key | 刪除元素/ rpop |
LREM key count value | 刪除指定個(gè)數(shù) |
Hash類(lèi)型
例:user { name:juhao婉称, age:18 }
key(鍵) field(域) value(值
HSET key field value | 設(shè)置 |
---|---|
HMSET key field1 value1 field2 value2 … | 添加多個(gè)鍵值對(duì) |
HGET key field | 獲取value |
HMGET key field field | 獲取多個(gè)value |
HVALS key | 獲取全部的values |
HKEYS key | 獲取所有的field |
HGETALL key | 獲取所有field和value |
Set類(lèi)型
Set類(lèi)型沒(méi)有排序的字符集合。
如果多次添加相同元素构蹬,Set中將僅保留該元素的一份拷貝
SADD key member1 [member2..] | 增加 |
---|---|
SMEMBERS key | 查看所有元素 |
SREM key member1 [member2..] | 指定刪除 |
SISMEMBER key member | 判斷集合是否存在某個(gè)值 |
SMEMBERS key | 查看set里面的元素 |
Sorted Set類(lèi)型
每一個(gè)成員都會(huì)有一個(gè)分?jǐn)?shù)(score)與之關(guān)聯(lián)王暗。
成員是唯一的,但是分?jǐn)?shù)(score)卻是可以重復(fù)的怎燥。
zadd key score1 member1 [score2 member2] | 設(shè)置 |
---|---|
zcount key min max | 返回score值在區(qū)間的元素個(gè)數(shù) |
zcard key | 返回zset元素個(gè)數(shù) |
zscore KEY MEMBER | 查看score值 |
zrange key start stop | 按照索引返回區(qū)間成員 |
Zrangebyscore key min max | 按照score值返回區(qū)間成員 |
zrem key member [member …] | 刪除 |
Redis配置
Redis配置文件
在Ubuntu系統(tǒng)中默認(rèn)配置文件地址: /etc/redis/redis.conf
port 5739 # 默認(rèn)端口
logfile /var/log/redis.log # 日志文件位置
dbfilename dump.rdb # RDB持久化數(shù)據(jù)文件
dir /var/lib/redis #本地?cái)?shù)據(jù)庫(kù)存放目錄
bind 127.0.0.1 # 指定IP進(jìn)行監(jiān)聽(tīng)
requirepass yourpassword #密碼
Save 900 1 #持久化
Mongodb
mongodb介紹
非關(guān)系型數(shù)據(jù)庫(kù)(NoSQL)
開(kāi)源,免費(fèi),高性能的文檔存儲(chǔ)數(shù)據(jù)庫(kù)
高效存儲(chǔ)二進(jìn)制大對(duì)象
海量數(shù)據(jù)下瘫筐,性能優(yōu)越
庫(kù)級(jí)操作語(yǔ)句
顯示所有庫(kù): show dbs
切換/創(chuàng)建數(shù)據(jù)庫(kù): use 數(shù)據(jù)庫(kù)名稱(chēng)
查看所在庫(kù): db
刪除庫(kù):db.dropDatabase()
集合(表)操作語(yǔ)句
查看當(dāng)前數(shù)據(jù)庫(kù)的集合: show collections
集合創(chuàng)建: db.createCollection(name)
刪除集合:db.集合名稱(chēng).drop()
數(shù)據(jù)級(jí)(文檔) 增刪查改 操作
插入文檔
db.集合名稱(chēng).insert(document)
每一條數(shù)據(jù),就是一個(gè)document铐姚,就是一條json
例: db.student.insert({name:‘juhao',age:18})
插入文檔時(shí)策肝,如果不指定_id參數(shù) MongoDB會(huì)為文檔分配一個(gè)唯一的ObjectId
例: db.student.insert({_id: 1, name: 'juhao', age:18})
插入多條:
db.student.insert([
{name:'juhao', sex:'男', age:18},
{name:'nanbei', sex:'男', age:18},
{name:'budong', sex:'男', age:18},
])
查詢文檔
db.集合名稱(chēng).find()
例: db.student.find()
例: db.student.insert().pretty()
高級(jí)查詢
and條件 {$and:[{expression1}, {expression1}, ...] }
or條件 {$or:[{expression1}, {expression1}, ...] }
操作符 | 描述 |
---|---|
$ne | 不等于 |
$gt | 大于 |
$lt | 小于 |
$gte | 大于等于 |
$lte | 小于等于 |
更新文檔
db.集合名稱(chēng).update(<query>, <update>, {multi: <boolean>} )
全文檔替換:
db.table.update({sex:'男'},{age:20})
指定屬性更新: { $set: {age:20} }
db.table.update({name:'juhao'}, {$set: {age:666, sex: 'xx'}} )
更新多條: { multi: ture }
db.table.update({sex:'男'}, {$set:{sex:'女'}}, { multi:true} )
刪除文檔
db.集合名稱(chēng). remove(<query>, {justOne: <boolean>)
刪除滿足條件的所有數(shù)據(jù):
db.table.remove({sex: '男’} )
刪除集合所有的文檔
db.table.remove({})
只刪除一條數(shù)據(jù): { justOne: true }
db.table.remove( {sex:’男'},{$set:{sex:'女'}},{ multi:true} )
Python操縱mongodb
程序操縱mongodb
安裝python包:pip install pymongo
建立連接: client= pymongo.MongoClient('127.0.0.1', 27017)
指定數(shù)據(jù)庫(kù):db=client[ 數(shù)據(jù)庫(kù)名 ]
指定集合:collection=db [ 集合名]
Python工具庫(kù)
datetime與logging模塊
logging
日志五個(gè)級(jí)別
日志等級(jí)(level) | 描述 |
---|---|
DEBUG | 調(diào)試信息,通常在診斷問(wèn)題的時(shí)候用得著 |
INFO | 普通信息,確認(rèn)程序按照預(yù)期運(yùn)行 |
WARNING | 警告信息,表示發(fā)生意想不到的事情,或者指示接下來(lái)可能會(huì)出現(xiàn)一些問(wèn)題,但是程序還是繼續(xù)運(yùn)行 |
ERROR | 錯(cuò)誤信息,程序運(yùn)行中出現(xiàn)了一些問(wèn)題,某些功能沒(méi)有執(zhí)行 |
CRITICAL | 危險(xiǎn)信息,一個(gè)嚴(yán)重的錯(cuò)誤,導(dǎo)致程序無(wú)法繼續(xù)運(yùn)行 |
Logging使用
函數(shù) | 函數(shù) |
---|---|
logging.log(level, message) | 創(chuàng)建一條嚴(yán)重級(jí)別為level的日志記錄 |
logging.basicConfig() | 對(duì)logger進(jìn)行配置 |
Formatter格式
%(asctime)s | 日志事件發(fā)生的時(shí)間 |
---|---|
%(levelname)s | 該日志記錄的日志級(jí)別 |
%(message)s | 日志記錄的文本內(nèi)容 |
%(name)s | 所使用的日志器名稱(chēng),默認(rèn)是'root' |
%(pathname)s | 調(diào)用日志記錄函數(shù)的文件的全路徑 |
%(filename)s | 調(diào)用日志記錄函數(shù)的文件 |
%(funcName)s | 調(diào)用日志記錄函數(shù)的函數(shù)名 |
%(lineno)d | 調(diào)用日志記錄函數(shù)的代碼所在的行號(hào) |
Logging與print差異
print()確實(shí)方便和易用,但是也有缺點(diǎn),比如打印出來(lái)的信息不能保存,再次運(yùn)行程序,之前打印出來(lái)的結(jié)果被清空
日志記錄包含清晰可見(jiàn)的診斷信息,如文件名稱(chēng),路徑,函數(shù)名和行號(hào)等
模塊化組件
組件 | 說(shuō)明 |
---|---|
Loggers(日志記錄器) | 提供程序直接使用的接口 |
Handlers(日志處理器) | 將記錄的日志發(fā)送到指定的位置 |
Filters(日志過(guò)濾器) | 對(duì)日志記錄進(jìn)行更詳細(xì)的輸出 |
Formatters(日志格式器) | 用于控制日志信息的輸出格式 |
模塊化組件使用
1.初始化一個(gè)logger對(duì)象
2.定義handler,決定把日志發(fā)到哪里
常用的是:
StreamHandler:將日志在控制臺(tái)輸出
FileHandler: 將日志記錄到文件里面
3.設(shè)置日志級(jí)別(level)和輸出格式(Formatter)
4.把handler添加到logger中去
日期與時(shí)間
datetime是python處理時(shí)間和日期的標(biāo)準(zhǔn)庫(kù)
類(lèi)名 | 功能說(shuō)明 |
---|---|
date | 日期對(duì)象,常用的屬性有year, month, day |
time | 時(shí)間對(duì)象hour隐绵,minute之众,second,毫秒 |
datetime | 日期時(shí)間對(duì)象,常用的屬性有hour, minute, second, microsecond |
timedelta | 時(shí)間間隔依许,即兩個(gè)時(shí)間點(diǎn)之間的長(zhǎng)度 |
主要使用: datetime.datetiem( ) 棺禾、 datetime.timedelta( )
實(shí)用函數(shù)
當(dāng)前日期時(shí)間: datetime.now()
日期時(shí)間轉(zhuǎn)換為時(shí)間戳:datetime.timestamp()
時(shí)間戳轉(zhuǎn)換為日期時(shí)間: datetime.fromtimestamp(時(shí)間戳)
字符串轉(zhuǎn)換為日期時(shí)間: datetime.strptime(data_str, format)
日期時(shí)間轉(zhuǎn)字符串:datetime.strftime(format)
時(shí)間計(jì)算
datetime.timedelta( days=0,seconds=0, microseconds=0 milliseconds=0,minutes=0, hours=0, weeks=0 )
格式字符串 常用格式
格式 | 描述 |
---|---|
%Y/%y | 年 |
%m | 月 |
%d | 日 |
%H/%l | 時(shí) |
%M | 分 |
%S | 秒 |
json與base64與hashlib模塊
json模塊
全名是JavaScript Object Notation(即:JavaScript對(duì)象表示法)它是JavaScript的子集。
輕量級(jí)的文本數(shù)據(jù)交換格式
易于閱讀和編寫(xiě)峭跳,同時(shí)也易于機(jī)器解析和生成
互聯(lián)網(wǎng)當(dāng)中最理想的數(shù)據(jù)交換語(yǔ)言
前后端數(shù)據(jù)交互
JS對(duì)象 | JSON字符串 | Python字典 |
---|---|---|
var teacher_1 = { |
name: ‘juhao’,
age: 18,
feature : [‘高’, ‘富’, ‘帥’]
} | {
“name”: “juhao”,
“age”: 18,
“ feature “ : [‘高’, ‘富’, ‘帥’]
} | {
‘name’: ‘juhao’,
‘a(chǎn)ge’: 18
‘feature’ : [‘高’, ‘富’, ‘帥’]
} |
Json語(yǔ)法規(guī)則
數(shù)據(jù)在鍵/值對(duì)中 ; 數(shù)據(jù)有逗號(hào)分隔; 大括號(hào)保存對(duì)象 ; 中括號(hào)保存數(shù)組
Json語(yǔ)法規(guī)則
JSON注意事項(xiàng):
名稱(chēng)必須用雙引號(hào)(即:””)來(lái)包括
值可以是雙引號(hào)包括的字符串膘婶、數(shù)字、true蛀醉、false悬襟、null、數(shù)組拯刁,或?qū)ο蟆?
Python | JSON |
---|---|
字典 | 對(duì)象 |
列表或元組 | 數(shù)組 |
字符串 | 字符串 |
int或float | 數(shù)字 |
True或False | true或false |
None | null |
json模塊 API
接口一:json.dumps(obj) | #把python的數(shù)據(jù)轉(zhuǎn)化成json字符串(indent實(shí)現(xiàn)縮進(jìn), sort_keys 實(shí)現(xiàn)排序, ensure_ascii實(shí)現(xiàn)是否用ascii解析) |
---|---|
接口二:json.loads(s) | #將JSON數(shù)據(jù)脊岳,轉(zhuǎn)換成Python的數(shù)據(jù)類(lèi)型 |
接口三:json.dump(obj, fp) | #轉(zhuǎn)換為json并保存到文件中 |
接口四:json.load(fp) | #從文件中讀取json,并轉(zhuǎn)化為python數(shù)據(jù)類(lèi)型 |
hashlib模塊
Hash(哈希)
hash也叫做散列函數(shù)
可以把任意長(zhǎng)度的數(shù)據(jù)轉(zhuǎn)換為,一個(gè)長(zhǎng)度固定的數(shù)據(jù)串
特點(diǎn):不可逆垛玻,唯一
用處:1.數(shù)據(jù)加密 2.數(shù)據(jù)查找
Hashlib模塊常用函數(shù)
Hashlib模塊提供了許多的hash算法,包括
md5, sha1, sha224, sha256, sha384, sha512
api | 描述 |
---|---|
hashlib.new(name,data='b') | 生產(chǎn)一個(gè)hash對(duì)象 |
hashlib.update(arg=None) | 更新hash對(duì)象 |
hash.digest() | 返回hash算法計(jì)算得到的數(shù)值(bytes類(lèi)型) |
hash.hexdigest() | 返回hash算法計(jì)算得到的數(shù)值(str類(lèi)型) |
hashlib.pbkdf2_hmac(name,password,salt,rounds,dklen=None) | 用于密碼加密,返回值是加密后的數(shù)值 |
base64模塊(了解)
Base64是一種用64個(gè)字符來(lái)表示任意的二進(jìn)制數(shù)據(jù)的方法
將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成可打印字符割捅,方便傳輸數(shù)據(jù)
對(duì)數(shù)據(jù)進(jìn)行簡(jiǎn)單的加密,肉眼安全帚桩。
適用于小段內(nèi)容的編碼亿驾,比如URL
base64模塊常用函數(shù)
api | 描述 |
---|---|
base64.b64encode(s) | 編碼 |
base64.b64decode(s) | 解碼 |
base64.urlsafe_b64encode(s) | 對(duì)url進(jìn)行編碼 |
base64.urlsafe_b64decode(s) | 對(duì)url進(jìn)行解碼 |
網(wǎng)絡(luò)通信
傳輸模型與套接字
傳輸模型
知識(shí)點(diǎn)一: 基本模型
知識(shí)點(diǎn)二: 層次的劃分
知識(shí)點(diǎn)三: 傳輸層說(shuō)明
知識(shí)點(diǎn)一: 基本模型
知識(shí)點(diǎn)二: 層次的劃分
知識(shí)點(diǎn)三: 傳輸層說(shuō)明
說(shuō)明一:
作為Python開(kāi)發(fā),咱們都是在應(yīng)用層的HTTP協(xié)議之上進(jìn)行開(kāi)發(fā)的账嚎。
說(shuō)明二:
網(wǎng)絡(luò)編程颊乘,主要是了解我們Python能編寫(xiě)的最低的層次参淹, 即傳輸層的基本情況
說(shuō)明三:
HTTP協(xié)議是基于TCP之上的 因此我們需要了解TCP連接的基本過(guò)程
傳輸模型總結(jié)
層次劃分
傳輸層
TCP協(xié)議
TCP連接
TCP連接 知識(shí)點(diǎn)
知識(shí)點(diǎn)一: 建立連接(三次握手)
知識(shí)點(diǎn)二: 傳輸數(shù)據(jù)
知識(shí)點(diǎn)三: 斷開(kāi)連接(四次揮手)
TCP連接總結(jié)
建立連接
傳輸消息
斷開(kāi)連接
IP地址與端口
IP地址與端口知識(shí)點(diǎn)
知識(shí)點(diǎn)一: IP地址
知識(shí)點(diǎn)二: 端口號(hào)
私有(內(nèi)網(wǎng))IP
IPv4地址協(xié)議中預(yù)留了3個(gè)IP地址段,作為私有地址乏悄,供組織機(jī)構(gòu)內(nèi)部使用
私有地址可以自己組網(wǎng)時(shí)用浙值,但不能在外網(wǎng)上用
這些地址的計(jì)算機(jī)要上網(wǎng)必須轉(zhuǎn)換成為合法的IP地址,也就是公網(wǎng)地址
分類(lèi) | 范圍 |
---|---|
A類(lèi) | 10.0.0.0-10.255.255.255 |
B類(lèi) | 172.16.0.0-172.31.255.255 |
C類(lèi) | 192.168.0.0-192.168.255.255 |
知識(shí)點(diǎn)二: 端口號(hào)
端口號(hào):用來(lái)唯一標(biāo)識(shí)應(yīng)用程序
每個(gè)應(yīng)用程序都占了一個(gè)端口
共有65535個(gè)端口
TCP連接總結(jié)
程序通信
先找IP
再找端口
套接字
套接字知識(shí)點(diǎn)
知識(shí)點(diǎn)一: 創(chuàng)建套接字
知識(shí)點(diǎn)二: 建立套接字連接
知識(shí)點(diǎn)三: 使用套接字傳輸數(shù)據(jù)
知識(shí)點(diǎn)四: 斷開(kāi)套接字連接
創(chuàng)建套接字
套接字基本概念
TCP連接兩個(gè)計(jì)算機(jī)進(jìn)行通信檩小,一條線會(huì)有兩個(gè)端點(diǎn)开呐,我們把連接兩端的端點(diǎn)稱(chēng)為套接字(socket)
創(chuàng)建套接字
導(dǎo)入socket模塊:import socket
創(chuàng)建socket對(duì)象:socket = socket.socket()
建立套接字連接
建立套接字連接要點(diǎn)
服務(wù)端套接字的綁定與監(jiān)聽(tīng)
客戶端套接字主動(dòng)連接
服務(wù)端對(duì)等連接套接字的生成
阻塞說(shuō)明
使用套接字傳輸
使用套接字傳輸要點(diǎn)
客戶端發(fā)送請(qǐng)求數(shù)據(jù)到服務(wù)端
服務(wù)端接受來(lái)自客戶端的請(qǐng)求數(shù)據(jù)
服務(wù)端向客戶端返回響應(yīng)數(shù)據(jù)
客戶端接收來(lái)自服務(wù)端的響應(yīng)數(shù)據(jù)
斷開(kāi)套接字連接
客戶端主動(dòng)斷開(kāi)連接
客戶端判斷客戶端斷開(kāi)連接后才斷開(kāi)連接
TCP連接總結(jié)
建立socket
通信
斷開(kāi)socket
六個(gè)方法
操作一: 服務(wù)器套接字綁定IP端口:bind
操作二: 服務(wù)器套接字監(jiān)聽(tīng):listen
操作三: 客戶端套接字連接服務(wù)器:connect
操作四: 套接字發(fā)送消息:send
操作五: 套接字接受消息:recv
操作六: 套接字關(guān)閉連接:close
非阻塞套接字與IO多路復(fù)用
基本IO模型
套接字問(wèn)題
問(wèn)題一: 普通套接字實(shí)現(xiàn)的服務(wù)端有什么缺陷嗎? (一次只能服務(wù)一個(gè)客戶端 规求! )
問(wèn)題二: 這種缺陷是如何造成的筐付?
accept阻塞: 當(dāng)沒(méi)有套接字連接請(qǐng)求過(guò)來(lái)的時(shí)候!會(huì)一直等待著
recv阻塞:沒(méi)有接受到客戶端發(fā)過(guò)來(lái)數(shù)據(jù)的時(shí)候!也會(huì)一直等待著
非阻塞套接字與非阻塞IO模型
非阻塞套接字問(wèn)題
問(wèn)題一: 非阻塞套接字是什么阻肿?
問(wèn)題二:非阻塞套接字與普通套接字的區(qū)別在哪里瓦戚?
try:
conn.setblocking(False) #對(duì)等套接字設(shè)置非阻塞套接字
except BlockingIOError:
pass
問(wèn)題三:非阻塞套接字的使用有什么注意事項(xiàng)?
使用非阻塞套接字實(shí)現(xiàn)并發(fā)
非阻塞套接字問(wèn)題
問(wèn)題一: 什么叫并發(fā)丛塌?
問(wèn)題二: 編程范式又是什么较解?
問(wèn)題三: 使用非阻塞套接字最終實(shí)現(xiàn)了什么?
整體思路
寧可用 while True 赴邻,也不要阻塞 發(fā)呆印衔!
將代碼順序重排,避開(kāi)阻塞姥敛!
吃滿 CPU 奸焙!并發(fā)服務(wù)多個(gè)客戶端 !
非阻塞套接字總結(jié)
非阻塞套接字
While True吃滿CPU
重排代碼順序避開(kāi)阻塞
IO多路復(fù)用
問(wèn)題引入
問(wèn)題一:非阻塞套接字服務(wù)器 還有什么不完美的嘛 彤敛?
問(wèn)題二: IO多路復(fù)用是什么与帆?
問(wèn)題三: epoll又是什么?
非阻塞不完美的地方
關(guān)鍵一: 任何操作都是需要花CPU資源的 墨榄!
關(guān)鍵二: 如果數(shù)據(jù)還沒(méi)有到達(dá)玄糟,那么 accept、recv操作都是無(wú)效的CPU花費(fèi) 渠概!
關(guān)鍵三: 對(duì)應(yīng)BlockingIOError的異常處理 也是無(wú)效的CPU花費(fèi) !
不完美的CPU利用率
IO多路復(fù)用 技術(shù)
把socket交給操作系統(tǒng)去監(jiān)控
IO多路復(fù)用技術(shù)
epoll 是真正的答案 嫂拴!
目前Linux上效率最高的 IO多路復(fù)用 技術(shù) 播揪!
epoll
epoll 基于惰性的事件回調(diào)機(jī)制
惰性的事件回調(diào)是由用戶自己調(diào)用的,操作系統(tǒng)只起到通知的作用
IO多路復(fù)用選擇器
IO多路復(fù)用選擇器是提供給我們調(diào)用epol的接口
使用步驟一:注冊(cè)事件回調(diào) epoll_selector.register(server,selectors.EVENT_READ,accept)
使用步驟二:事件回調(diào) epoll_selector.unregister(connection)
IO多路復(fù)用總結(jié)
注冊(cè)事件及回調(diào)
查詢
回調(diào)
網(wǎng)絡(luò)編程
并行與并發(fā)的深入理解
問(wèn)題一: 計(jì)算機(jī)是如何執(zhí)行程序指令的筒狠?
問(wèn)題二: 計(jì)算機(jī)如何實(shí)現(xiàn)并發(fā)的猪狈?
問(wèn)題三: 真正的并行需要依賴(lài)什么?
多進(jìn)程并行問(wèn)題
問(wèn)題一: 什么是進(jìn)程辩恼?
問(wèn)題二: 如何在Python中使用進(jìn)程雇庙?
問(wèn)題三: 多進(jìn)程實(shí)現(xiàn)并行的必要條件是什么谓形?
進(jìn)程
進(jìn)程的概念
計(jì)算機(jī)程序是存儲(chǔ)在磁盤(pán)上的文件。
只有把它們加載到內(nèi)存中疆前,并被操作系統(tǒng)調(diào)用 它們才會(huì)擁有其自己的生命周期寒跳。
進(jìn)程表示一個(gè)正在執(zhí)行的程序。
每個(gè)進(jìn)程都有獨(dú)立地址空間以及其他的輔助數(shù)據(jù)
進(jìn)程(Process)
是計(jì)算機(jī)中已運(yùn)行程序的實(shí)例
多進(jìn)程并行的必要條件
總進(jìn)程數(shù)量不多于 CPU核心數(shù)量竹椒!
如果不滿足童太,那么運(yùn)行的程序都是 輪詢調(diào)度產(chǎn)生的假象。
在Python中使用進(jìn)程
improt multiprocessing
import datetime
import time
print('mainpeocess start',datetime.datetime.now())
def func():
print('msubpeocess start',datetime.datetime.now())
time.sleep(5)
print('subpeocess end',datetime.datetime.now())
p = multiprocessing.Process(target=func)
p.start()
time.sleep(5)
print('mainpeocess end',datetime.datetime.now())
線程
多線程實(shí)現(xiàn)并發(fā)
多線程并發(fā)問(wèn)題
問(wèn)題一: 什么是線程胸完?
問(wèn)題二: 如何在Python中使用線程书释?
問(wèn)題三: 為什么多線程不是并行?
線程的概念
線程被稱(chēng)作輕量級(jí)進(jìn)程赊窥。
線程是進(jìn)程中的一個(gè)實(shí)體爆惧,操作系統(tǒng)不會(huì)為進(jìn)程分配內(nèi)存空間,它只有一點(diǎn)在運(yùn)行中必不可少的資源
線程被包含在進(jìn)程中,是進(jìn)程中的實(shí)際運(yùn)作單位
同一個(gè)進(jìn)程內(nèi)的多個(gè)線程會(huì)共享相同的上下文锨能,也就是共享資源(內(nèi)存和數(shù)據(jù))扯再。
線程(thread)
是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位
在Python中使用線程
improt threading
import datetime
import time
print('mainThread start',datetime.datetime.now())
def func():
print('subThread start',datetime.datetime.now())
time.sleep(5)
print('subThread end',datetime.datetime.now())
p = threading.Thread(target=func)
p.start()
time.sleep(5)
print('Thread end',datetime.datetime.now())
線程VS進(jìn)程
穩(wěn)定性
進(jìn)程具有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后腹侣,不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響叔收。
線程共享地址空間,一個(gè)線程非法操作共享數(shù)據(jù)崩潰后傲隶,整個(gè)進(jìn)程就崩潰了饺律。
創(chuàng)建開(kāi)銷(xiāo)
創(chuàng)建進(jìn)程操作系統(tǒng)是要分配內(nèi)存空間和一些其他資源的。開(kāi)銷(xiāo)很大
創(chuàng)建線程操作系統(tǒng)不需要再單獨(dú)分配資源跺株,開(kāi)銷(xiāo)較小
切換開(kāi)銷(xiāo)
不同進(jìn)程直接是獨(dú)立的复濒, 切換需要耗費(fèi)較大資源
線程共享進(jìn)程地址空間, 切換開(kāi)銷(xiāo)小
GIL鎖(線程鎖)
Python在設(shè)計(jì)的時(shí)候乒省,還沒(méi)有多核處理器的概念巧颈。
因此,為了設(shè)計(jì)方便與線程安全袖扛,直接設(shè)計(jì)了一個(gè)鎖砸泛。
這個(gè)鎖要求,任何進(jìn)程中蛆封,一次只能有一個(gè)線程在執(zhí)行唇礁。
因此,并不能為多個(gè)線程分配多個(gè)CPU惨篱。
所以Python中的線程只能實(shí)現(xiàn)并發(fā)盏筐,
而不能實(shí)現(xiàn)真正的并行。
但是Python3中的GIL鎖有一個(gè)很棒的設(shè)計(jì)砸讳,
在遇到阻塞(不是耗時(shí))的時(shí)候琢融,會(huì)自動(dòng)切換線程界牡。
GIL鎖(線程鎖)
遇到阻塞就自動(dòng)切換。我們可以利用這種機(jī)制來(lái)充分利用CPU
使用多進(jìn)程與多線程來(lái)實(shí)現(xiàn)并發(fā)服務(wù)器
使用多進(jìn)程與多線程實(shí)現(xiàn)并發(fā)服務(wù)器的關(guān)鍵點(diǎn)
關(guān)鍵點(diǎn)一: 多進(jìn)程是并行執(zhí)行漾抬, 相當(dāng)于分別獨(dú)立得處理各個(gè)請(qǐng)求宿亡。
關(guān)鍵點(diǎn)二: 多線程,雖然不能并行運(yùn)行奋蔚, 但是可以通過(guò)避開(kāi)阻塞切換線程 來(lái)實(shí)現(xiàn)并發(fā)的效果她混,并且不浪費(fèi)cpu
使用多線程實(shí)現(xiàn)并發(fā)服務(wù)器
import threading
import socket
server = socket.socket()
server.bin(('0.0.0.0',8000))
server.listen(1000)
def worker(connection):
while True:
recv_data = connection.recv(1024)
if recv_data:
print(recv_data)
else:
connection.close()
break
while True:
connection,remote_address = server.accept()
process = threading.Thread(target=worker,args=(conncetion,))
process.start()
進(jìn)程與線程的標(biāo)識(shí)
知識(shí)點(diǎn)一:進(jìn)程id(process.pid) 與 線程ident(thread.ident)
知識(shí)點(diǎn)二:進(jìn)程名(process.name) 與 線程名(thread.name)
知識(shí)點(diǎn)三:獲取當(dāng)前線程(thread.current_thread()) /進(jìn)程信息(process.current_process())
進(jìn)程與線程的其余相關(guān)操作
知識(shí)點(diǎn)一:等待結(jié)束 eg:process.start()
知識(shí)點(diǎn)三:進(jìn)程與線程的生存狀態(tài) eg:process.is_aliver()
知識(shí)點(diǎn)二:終止進(jìn)程 eg:process.terminate()
等待進(jìn)程或線程結(jié)束
improt multiprocessing as mp
improt time
import datetime
def func():
time.sleep(5)
print(datetime.datetime.now())
process = mp.Process(target=func,name='進(jìn)程名字')
process.start() # 開(kāi)啟進(jìn)程
process.join() # 阻塞,等待子進(jìn)程運(yùn)行完后 再進(jìn)行運(yùn)行
time.sleep(5)
print()
print('main-process end')
守護(hù)模式
提示!
多線程中的守護(hù)線程與守護(hù)進(jìn)程類(lèi)似: process = mp.Process(target=func,daemon=True)
總結(jié)
進(jìn)程/線程 一些標(biāo)識(shí)
Join等待
守護(hù)模式daemon
以面向?qū)ο蟮男问绞褂眠M(jìn)程與線程
面向?qū)ο笫褂镁€程/進(jìn)程
步驟一: 繼承 Process或Thread 類(lèi)
步驟二: 重寫(xiě) __init__方法
步驟三: 重寫(xiě) run方法
面向?qū)ο蠡褂眠M(jìn)程
import multiprocessing as mp
class My_Process(mp.Process):
def __init__(self,*args,**kwargs):
super().__init__()
self.args = args
self.kwargs = kwargs
def run(self):
print(mp.current_process())
print(self.args)
print(self.kwargs)
print(mp.current_process())
print(" --- "*10 )
p = My_Process(1,2,3,a=1,b=2,c=3)
p.start()
面向?qū)ο笫褂玫乃悸?/p>
繼承
調(diào)用父類(lèi)初始化
重寫(xiě)run方法
獨(dú)立的進(jìn)程內(nèi)存空間與共享的服務(wù)器進(jìn)程空間
多進(jìn)程之間的通信
問(wèn)題一: 多個(gè)進(jìn)程之間通信有什么限制嗎泊碑?
問(wèn)題二: 這種限制產(chǎn)生的原因坤按?
問(wèn)題三: Manger對(duì)象又是什么?
多進(jìn)程之間通信的限制
進(jìn)程之間是獨(dú)立的馒过,互不干擾的內(nèi)存空間
進(jìn)程間通信的解決方案
一般常用的空間類(lèi)型是:
1. mgr.list()
2. mgr.dict()
3. mgr.Queue()
關(guān)于Queue()我們稍后講解
from multiprocessing import Process,Manager
mgr = Manager()
list_proxy = mgr.list()
print(list_proxy)
def func(list):
list.append('a')
p = Process(target=func,args=(list_proxy,))
p.start()
p.join()
print(list_proxy)
多進(jìn)程之間通信總結(jié)
獨(dú)立空間變量不共享
需要借助第三方進(jìn)程
Manager對(duì)象
線程間共享的全局變量與同步鎖的基本概念
多線程之間的通信
問(wèn)題一: 線程間也會(huì)出現(xiàn)進(jìn)程間通信的問(wèn)題嘛臭脓?
問(wèn)題二: 如果不會(huì),是否會(huì)產(chǎn)生一些問(wèn)題腹忽?
問(wèn)題三: 有沒(méi)有解決這個(gè)問(wèn)題的方法来累?
多線程之間通信的限制
improt threading as th
a = 1
def func():
global a
a = 2
thread = th.Thread(target =func )
thread.start()
thread.join()
print(a)
提示!
因?yàn)榫€程屬于同一個(gè)進(jìn)程窘奏,因此它們之間共享內(nèi)存區(qū)域嘹锁。
因此全局變量是公共的。
共享內(nèi)存間存在競(jìng)爭(zhēng)問(wèn)題
提示着裹!
如果1000000不能出現(xiàn)效果
可以繼續(xù)在后面加0
使用鎖來(lái)控制共享資源的訪問(wèn)
improt threading as th
lock = th.Lock()
def func():
lock.acquire() # 枷鎖
...
lock.release() # 解鎖
多線程之間通信總結(jié)
共享內(nèi)存空間 變量共享
共享數(shù)據(jù) 需要加鎖
競(jìng)爭(zhēng)問(wèn)題
線程與進(jìn)程 安全的隊(duì)列
隊(duì)列的基本概念
一個(gè)入口领猾,一個(gè)出口
先入先出(FIFO)
線程安全隊(duì)列 操作一覽
queue.Queue
入隊(duì): put(item)
出隊(duì): get()
測(cè)試空: empty() # 近似
測(cè)試滿: full() # 近似
隊(duì)列長(zhǎng)度: qsize() # 近似
任務(wù)結(jié)束: task_done()
等待完成: join()
進(jìn)程安全隊(duì)列 操作一覽
mgr.Queue
入隊(duì): put(item)
出隊(duì): get()
測(cè)試空: empty() # 近似
測(cè)試滿: full() # 近似
隊(duì)列長(zhǎng)度: qsize() # 近似
生產(chǎn)者與消費(fèi)者模型
生產(chǎn)-消費(fèi)模型問(wèn)題
問(wèn)題一: 什么是生產(chǎn)者與消費(fèi)者模型?
問(wèn)題二: 為什么需要生產(chǎn)者與消費(fèi)者模型骇扇?
問(wèn)題三: 如何通過(guò)隊(duì)列實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者模式摔竿?
生產(chǎn)者與消費(fèi)者模型的概念
所謂,生產(chǎn)者與消費(fèi)者模型少孝,本質(zhì)上是把進(jìn)程通信的問(wèn)題分開(kāi)考慮
生產(chǎn)者继低,只需要往隊(duì)列里面丟東西(生產(chǎn)者不需要關(guān)心消費(fèi)者)
消費(fèi)者,只需要從隊(duì)列里面拿東西(消費(fèi)者也不需要關(guān)心生產(chǎn)者)
消費(fèi)者與生產(chǎn)者模式的應(yīng)用
Web服務(wù)器與Web框架之間的關(guān)系
多線程的消費(fèi)者與生產(chǎn)者模式
生產(chǎn)者:
只關(guān)心隊(duì)列是否已滿稍走。
沒(méi)滿袁翁,則生產(chǎn),滿了就阻塞婿脸。
消費(fèi)者:
只關(guān)心隊(duì)列是否為空粱胜。
不為空,則消費(fèi)盖淡,為空則阻塞年柠。
安全隊(duì)列凿歼, 生產(chǎn)者與消費(fèi)者模型總結(jié)
不用考慮 內(nèi)存和鎖的問(wèn)題
非常重要
通過(guò)隊(duì)列實(shí)現(xiàn)
可重復(fù)利用的 線程
from threading import Thread
from queue import Queue
class MyThread(Thread):
def __init__():
self.queue = Queue()
self.daemon = True
self.start()
def run(self):
while True:
func,args,kwargs = self.queue.get()
func(*args,**kwargs)
def apply_async(self,func,*args,**kwargs):
self.queue.put((func,args,kwargs))
def join(self):
self.queue.join()
def func1():
time.sleep(2)
print(" ok1 )
def func2(*args,**kwargs):
time.sleep(2)
print(" ok2 )
thread = MyThread()
thread.apply_async(func1)
thread.apply_async(func2,1,2,c=3,d=4)
print("ok")
thread.join()
print("ok")
線程池的 簡(jiǎn)單實(shí)現(xiàn)
池的概念
主線程: 相當(dāng)于生產(chǎn)者褪迟,只管向線程池提交任務(wù)冗恨。
并不關(guān)心線程池是如何執(zhí)行任務(wù)的。 因此味赃,并不關(guān)心是哪一個(gè)線程執(zhí)行的這個(gè)任務(wù)掀抹。
線程池: 相當(dāng)于消費(fèi)者,負(fù)責(zé)接收任務(wù)心俗, 并將任務(wù)分配到一個(gè)空閑的線程中去執(zhí)行傲武。
線程池的簡(jiǎn)單實(shí)現(xiàn)
from threading import Thread
from queue import Queue
import time
class ThreaPool:
def __init__(self,n):
self.queue = Queue()
for i in range(n):
Thread(target=self.worker,daemon=True).start()
def worker(self):
while Ture:
func,args,kwargs = self.queue.get()
func(*args,**kwargs)
self.queue.task_done()
def apply_async(self,func,*args,**kwargs)
self.queue.put((func,args,kwargs))
def join():
self.queue.join()
def func1():
time.sleep(2)
print(" ok1 )
def func2(*args,**kwargs):
time.sleep(2)
print(" ok2 )
pool = ThreadPool(2)
pool.apply_async(func1)
pool.apply_async(func2,1,2,c=3,d=4)
print("提交 ok")
pool.join()
print("完成 ok")
Python自帶池
池的操作
操作一: apply_async – 向池中提交任務(wù)
操作二: close - 關(guān)閉提交通道,不允許再提交任務(wù)
操作三: terminate - 中止進(jìn)程池城榛,中止所有任務(wù)
內(nèi)置線程池
from multiprocessing.pool import ThreadPool
import time
def func1():
time.sleep(2)
print(" ok1 )
def func2(*args,**kwargs):
time.sleep(2)
print(" ok2 )
pool = ThreadPool(2)
pool.apply_async(func1)
pool.apply_async(func1)
pool.apply_async(func2,1,2,c=3,d=4)
print("提交 ok")
pool.close() # 在join之前必須關(guān)閉提交 close,就不允許在提交任務(wù)了
pool.join() # 等待完成揪利,繼續(xù)下一個(gè)運(yùn)行;
print("完成 ok")
內(nèi)置的進(jìn)程池
from multiprocessing.pool import Pool
import time
def func1():
time.sleep(2)
print(" ok1 )
def func2(*args,**kwargs):
time.sleep(2)
print(" ok2 )
if __name__ = "__main__":
pool = Pool(2)
pool.apply_async(func1)
pool.apply_async(func1)
pool.apply_async(func2,1,2,c=3,d=4)
print("提交 ok")
pool.close() # 在join之前必須關(guān)閉提交 close,就不允許在提交任務(wù)了
pool.join() # 等待完成,繼續(xù)下一個(gè)運(yùn)行;
print("完成 ok")
使用池來(lái)實(shí)現(xiàn)并發(fā)服務(wù)器
使用線程池來(lái)實(shí)現(xiàn)并發(fā)服務(wù)器
from multiprocessing.pool import ThreadPool
import socket
server = socket.socket()
server.bin(('127.0.0.1',8000))
server.listen(100)
def woker(connection):
while True:
recv_data = connection.recv(1024)
if recv_data:
print(recv_data)
connection.send(recv_data)
else:
connection.close()
break
if __name__ = "__main__":
pool = ThreadPool(2)
while True:
connection,address = server.accept()
pool.apply_async(worker,args=(connection,))
使用進(jìn)程池+線程池來(lái)實(shí)現(xiàn)并發(fā)服務(wù)器
from multiprocessing.pool import Pool
from Pool import cup_count
import socket
server = socket.socket()
server.bin(('127.0.0.1',8000))
server.listen(100)
def woker_thread(connection):
while True:
recv_data = connection.recv(1024)
if recv_data:
print(recv_data)
connection.send(recv_data)
else:
connection.close()
break
def worker_process(server):
htread_pool = ThreadPool(cpu_count()*2)
while True:
connction,remote_address = server.accept()
thread_pool.async(worker_thread,args=(connection,))
if __name__ = "__main__":
n = cup_count()
process_pool = Pool(2)
for i in range(n):
process_pool.apply_async(worker_process,args=(server,))
process_pool.close()
process_pool.join()
聊天室
服務(wù)端
from multiprocessing import Pool, cpu_count,Manager
from multiprocessing.pool import ThreadPool
import socket
from datetime import datetime
#從隊(duì)列中拿出數(shù)據(jù)狠持,發(fā)給所有連接上的客戶端
def send_data(dict_proxy, queue_proxy):
while True:
data = queue_proxy.get()
print(data.decode())
for conn in dict_proxy.values():
conn.send(data)
def worker_thread(connection, addr, dict_proxy, queue_proxy):
while True:
try:
recv_data = connection.recv(1024)
if recv_data:
time = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
data = "{addr} {time} \n \t{data}".format(addr=addr, time = time, data=recv_data.decode())
queue_proxy.put(data.encode()) #把消息添加到到隊(duì)列中
else:
raise Exception
except:
dict_proxy.pop(addr) #從字典中刪掉退出的客戶端
data = '{}退出'.format(addr)
queue_proxy.put(data.encode()) #把退出消息添加到隊(duì)列中
connection.close()
break
def login(username,conncetion, thread_pool, dict_proxy, queue_proxy ):
dict_proxy.setdefault(username, conncetion) # 把套接字加入字典中
conncetion.send("恭喜你疟位,登陸成功".encode())
data = '{}登錄'.format(username)
queue_proxy.put(data.encode()) # 將用戶登錄消息添加到隊(duì)列中
thread_pool.apply_async(worker_thread, args=(conncetion, username, dict_proxy, queue_proxy))
def login_try(conncetion,thread_pool, dict_proxy,queue_proxy, data):
conncetion.send(data)
username = conncetion.recv(1024).decode()
if username not in dict_proxy:
login(username, conncetion, thread_pool, dict_proxy, queue_proxy)
else:
data = "用戶名已被使用,請(qǐng)重新輸入!".encode()
login_try(conncetion, thread_pool, dict_proxy, queue_proxy, data)
def worker_process(server, dict_proxy, queue_proxy):
thread_pool = ThreadPool( cpu_count()*2 )
while True:
conncetion, remote_address = server.accept()
data = "請(qǐng)輸入用戶名!".encode()
login_try(conncetion, thread_pool, dict_proxy, queue_proxy, data)
if __name__ == '__main__':
server = socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(1000)
mgr = Manager()
dict_proxy = mgr.dict() #用來(lái)保存連接上來(lái)的客戶端喘垂,
queue_proxy = mgr.Queue() #把客戶端發(fā)過(guò)來(lái)的消息通過(guò)隊(duì)列傳遞
n = cpu_count() #打印當(dāng)前電腦的cpu核數(shù)
process_pool = Pool(n)
for i in range(n-1): #充分利用CPU甜刻,為每一個(gè)CPU分配一個(gè)進(jìn)程
process_pool.apply_async(worker_process, args=(server, dict_proxy, queue_proxy)) #把server丟到兩個(gè)進(jìn)程里面
process_pool.apply_async(send_data, args=(dict_proxy, queue_proxy)) #用一個(gè)進(jìn)程去收發(fā)消息
process_pool.close()
process_pool.join()
客戶端
import socket
import threading
client = socket.socket()
client.connect(('127.0.0.1', 8888))
def recv_data():
while True:
data = client.recv(1024)
print(data.decode())
username = input("輸入你的用戶名:")
client.send(username.encode())
thread = threading.Thread(target=recv_data, daemon=True)
thread.start()
while True:
a = input('')
client.send(a.encode())
雙人聊天
服務(wù)器端
#coding:utf-8
#導(dǎo)入相關(guān)包
import socket
import sys
import time
ISOTIMEFORMAT='%Y-%m-%d %X' #時(shí)間格式
host='' #本機(jī)ip
port=8888 #端口號(hào)
#創(chuàng)建流式套接字
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#設(shè)置端口復(fù)用
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#綁定(ip,port)
s.bind((host,port))
#監(jiān)聽(tīng)套接字
s.listen(5)
print 'runing...'
#接受客戶端連接
ClientSock,ClientAddr=s.accept()
print '%s connected.'%(str(ClientAddr))
while 1:
try:
#接收消息
buf=ClientSock.recv(1024)
if len(buf): #消息長(zhǎng)度大于0則輸出
print "he say: "+buf
data=raw_input("I say: ") #等待用戶控制臺(tái)輸入
#格式化當(dāng)前時(shí)間
send_time=time.strftime(ISOTIMEFORMAT,time.localtime())
#發(fā)送消息
ClientSock.sendall(data+'[%s]'%(send_time))
except:
print "Dialogue Over"
ClientSock.close() #關(guān)閉套接字
sys.exit(0) #退出程序
客戶端
#coding:utf-8
#導(dǎo)入相關(guān)包
import socket
import sys
import time
ISOTIMEFORMAT='%Y-%m-%d %X' #時(shí)間格式
host='127.0.0.1' #本機(jī)地址
port=8888
#創(chuàng)建流式套接字
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
#連接套接字
s.connect((host,port))
except socket.gaierror,e:
print "Address-related error connecting to server:%s"%e
# 1表示非正常退出程序,退出程序是告訴解釋器(os)的
sys.exit(1)
except socket.error,e:
print "Connection error:%s"%e
sys.exit(1)
while 1:
try:
#等待用戶控制臺(tái)輸入
data=raw_input("I say: ")
#發(fā)送消息的時(shí)間
send_time=time.strftime(ISOTIMEFORMAT,time.localtime())
#向服務(wù)器發(fā)送消息
s.send(data+'[%s]'%(send_time))
#從服務(wù)器接收消息
buf=s.recv(1024)
if len(buf): #消息長(zhǎng)度大于0則輸出
print "he say: "+buf
except: #異常處理
print "Dialogue Over"
s.close() #關(guān)閉套接字
sys.exit(0) #0表示正常退出程序
協(xié)程
協(xié)程問(wèn)題
問(wèn)題一:生成器是什么正勒?
問(wèn)題二:生成器與普通函數(shù)的區(qū)別得院?
問(wèn)題三:協(xié)程是什么?
生成器語(yǔ)法
yield 一個(gè)對(duì)象:
返回這個(gè)對(duì)象
暫停這個(gè)函數(shù)
等待下次next重新激活
send與yield的切換
send 一個(gè)對(duì)象:
激活生成器
執(zhí)行生成器里面的代碼
遇到y(tǒng)ield回到調(diào)用位置
注意事項(xiàng)
對(duì)一個(gè)生成器必須要先next()讓他執(zhí)行到y(tǒng)ield才能在send數(shù)據(jù)進(jìn)去章贞。
攜程是在一個(gè)線程內(nèi)的執(zhí)行的祥绞,本質(zhì)來(lái)說(shuō)就是不同函數(shù)之間的切換調(diào)用。
如果某一個(gè)協(xié)程被阻塞了阱驾,整個(gè)線程(進(jìn)程)都被阻塞就谜。任意時(shí)刻,只有一個(gè)協(xié)程在執(zhí)行里覆。
greenlet
greenlet問(wèn)題
問(wèn)題一: 什么是greenlet 丧荐?
問(wèn)題二: 如何使用 greenlet ?
問(wèn)題三: 為什么需要greenlet 喧枷?
什么是 greenlet 虹统?
pip install greenlet
雖然CPython(標(biāo)準(zhǔn)Python)能夠通過(guò)生成器來(lái)實(shí)現(xiàn)協(xié)程, 但使用起來(lái)還并不是很方便隧甚。
Python的一個(gè)衍生版 Stackless Python 實(shí)現(xiàn)了原生的協(xié)程车荔,它更利于使用
于是,大家開(kāi)始將 Stackless 中關(guān)于協(xié)程的代碼 單獨(dú)拿出來(lái)做成了CPython的擴(kuò)展包戚扳。
這就是 greenlet 的由來(lái)忧便,greenlet 是底層實(shí)現(xiàn)了原生協(xié)程的 C擴(kuò)展庫(kù)。
greenlet 的基本使用
form greenlet import greenlet
def fn1():
pass
def fn2():
pass
c = greenlet(fn1)
p = greenlet(fn2)
c.switch()
greenlet 的價(jià)值
價(jià)值一: 高性能的原生協(xié)程
價(jià)值二: 語(yǔ)義更加明確的顯式切換
價(jià)值三: 直接將函數(shù)包裝成協(xié)程帽借,保持原有代碼風(fēng)格
gevent協(xié)程
gevent問(wèn)題
問(wèn)題一: 什么是 gevent 珠增?
問(wèn)題二:如何使用 gevent 超歌?
問(wèn)題三: gevent 的價(jià)值是什么 ?
什么是 gevent蒂教?
pip install gevent
雖然巍举,我們有了 基于 epoll 的回調(diào)式編程模式,但是卻難以使用凝垛。
即使我們可以通過(guò)配合 生成器協(xié)程 進(jìn)行復(fù)雜的封裝懊悯,以簡(jiǎn)化編程難度。
但是仍然有一個(gè)大的問(wèn)題: 封裝難度大梦皮,現(xiàn)有代碼幾乎完全要重寫(xiě)
gevent炭分,通過(guò)封裝了 libev(基于epoll) 和 greenlet 兩個(gè)庫(kù)。
幫我們做好封裝剑肯,允許我們以類(lèi)似于線程的方式使用協(xié)程欠窒。
以至于我們幾乎不用重寫(xiě)原來(lái)的代碼就能充分利用 epoll 和 協(xié)程 威力。
gevent 的價(jià)值
遇到阻塞就切換到 另一個(gè)協(xié)程繼續(xù)執(zhí)行 退子!
價(jià)值一: 使用基于 epoll 的 libev 來(lái)避開(kāi)阻塞
價(jià)值二: 使用基于 gevent 的 高效協(xié)程 來(lái)切換執(zhí)行
價(jià)值三: 只在遇到阻塞的時(shí)候切換岖妄, 沒(méi)有輪需的開(kāi)銷(xiāo),也沒(méi)有線程的開(kāi)銷(xiāo)
gevent 協(xié)程通信
gevent通信 問(wèn)題
問(wèn)題一:協(xié)程之間不是能通過(guò)switch通信嘛寂祥?
是的荐虐,由于 gevent 基于 greenlet,所以可以丸凭。
問(wèn)題二:那為什么還要考慮通信問(wèn)題福扬?
因?yàn)?gevent 不需要我們使用手動(dòng)切換拳昌, 而是遇到阻塞就切換金踪,因此我們不會(huì)去使用switch !
gevent
協(xié)程負(fù)責(zé) 主動(dòng)切換
IO多路復(fù)用 負(fù)責(zé)避開(kāi)阻塞
gevent
gevent.queue.Queue
import gevent
from gevent.queue import Queue
queue = Queue(3)
def fn1():
while True:
item = random.randint(0,99)
print("生產(chǎn): {}".format(item))
queue.put(item)
def fn2():
while True:
item = queue.get()
print("消費(fèi): {}".format(item))
p = gevent.spawn(fn1,queue)
c = gevent.spawn(fn2,queue)
gevent.joinall([p,c])
什么是協(xié)程
協(xié)程竞帽,又稱(chēng)微線程虽界,纖程汽烦,英文名Coroutine。協(xié)程的作用莉御,是在執(zhí)行函數(shù)A時(shí)撇吞,可以隨時(shí)中斷,去執(zhí)行函數(shù)B礁叔,然后中斷繼續(xù)執(zhí)行函數(shù)A(可以自由切換)牍颈。但這一過(guò)程并不是函數(shù)調(diào)用(沒(méi)有調(diào)用語(yǔ)句),這一整個(gè)過(guò)程看似像多線程琅关,然而協(xié)程只有一個(gè)線程執(zhí)行煮岁。
- 攜程
def func():
i=0
while True:
x = yield i
i += 1
print('func:', x)
a = func()
print(next(a))
a.send('hello')
a.send('nihao')
send() 可以恢復(fù)執(zhí)行,并把傳進(jìn)去的東西當(dāng)作yield的返回值
對(duì)一個(gè)生成器必須要先next()讓他執(zhí)行到y(tǒng)ield才能在send數(shù)據(jù)進(jìn)去。
函數(shù)里面加yield 就把一個(gè)函數(shù)變成了生成器画机,
生成器遇到y(tǒng)ield暫停運(yùn)行勤篮,并把后面的數(shù)據(jù)返回
通過(guò)yield實(shí)現(xiàn)了函數(shù)暫停運(yùn)行的效果
然后想要再次運(yùn)行這個(gè)函數(shù),通過(guò)next去激活這個(gè)生成器色罚,運(yùn)行生成器函數(shù)。
如果把調(diào)用next這個(gè)過(guò)程封裝成函數(shù)账劲,通過(guò)通過(guò)yield和next實(shí)現(xiàn)了兩個(gè)函數(shù)切換運(yùn)行的功能
生產(chǎn)者和消費(fèi)者模型
import random
import time
def producer(consumer): #生產(chǎn)者
next(consumer)
while True:
item = random.randint(0, 99) #隨機(jī)生產(chǎn)一個(gè)數(shù)
print('生產(chǎn)者:生產(chǎn)了%s'%(item))
consumer.send(item) #把item給消費(fèi)者
time.sleep(2) #每生產(chǎn)一個(gè)就休息一些戳护,方便我們看效果
def consumer(): #消費(fèi)者
#消費(fèi)者里面的東西是從生產(chǎn)者那里來(lái)的, 用yield來(lái)接收
while True:
item = yield
print('消費(fèi)者: 拿到%s' % item)
c = consumer() #生成一個(gè)消費(fèi)者
producer(c)
2. greenlet協(xié)程
yield能實(shí)現(xiàn)協(xié)程,不過(guò)實(shí)現(xiàn)過(guò)程不易于理解瀑焦,greenlet是在這方面做了改進(jìn)
from greenlet import greenlet
import random
import time
def producer():
while True:
item = random.randint(0, 99)
print('生產(chǎn)者: 生產(chǎn)了%s'%item)
c.switch(item) #將item傳給消費(fèi)者腌且, 并切換到消費(fèi)者,
time.sleep(2)
def consumer():
while True:
item = p.switch() #切換到生產(chǎn)者榛瓮,并等待生產(chǎn)者傳入item
print('消費(fèi)者: 拿到了%s'%item)
c = greenlet(consumer) #將一個(gè)普通的函數(shù)變成攜程
p = greenlet(producer)
c.switch() #運(yùn)行消費(fèi)者铺董,
3. gevent協(xié)程
import gevent
from gevent import monkey
monkey.patch_socket()
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(100)
def worker(connection):
while True:
try:
recv_data = connection.recv(1024)
if recv_data:
print(recv_data)
connection.send(recv_data)
else:
raise Exception
except:
connection.close()
break
while True:
connection, remote_address = server.accept()
gevent.spawn(worker, connection) #這就變成了一個(gè)攜程版本
猴子補(bǔ)丁,它把python自帶的socket替換掉禀晓,實(shí)現(xiàn)了封裝epoll的socket
這個(gè)socket遇到阻塞精续,就自動(dòng)切換攜程
只要你打了這個(gè)補(bǔ)丁, 以后你使用的socket只要遇到了阻塞就會(huì)切換攜程
協(xié)程 (Coroutine)
又稱(chēng)微線程粹懒,纖程重付,協(xié)程是一種用戶態(tài)的輕量級(jí)線程。
? 我們可以使用協(xié)程來(lái)實(shí)現(xiàn)異步操作凫乖,比如在網(wǎng)絡(luò)爬蟲(chóng)場(chǎng)景下确垫,我們發(fā)出一個(gè)請(qǐng)求之后,需要等待一定的時(shí)間才能得到響應(yīng)帽芽,但其實(shí)在這個(gè)等待過(guò)程中删掀,程序可以干許多其他的事情,等到響應(yīng)得到之后才切換回來(lái)繼續(xù)處理导街,這樣可以充分利用 CPU 和其他資源披泪,這就是異步協(xié)程的優(yōu)勢(shì)。
Python 中使用協(xié)程最常用的庫(kù)莫過(guò)于 asyncio搬瑰,所以本文會(huì)以 asyncio 為基礎(chǔ)來(lái)介紹協(xié)程的使用付呕。
首先我們需要了解下面幾個(gè)概念:
- event_loop:事件循環(huán),相當(dāng)于一個(gè)無(wú)限循環(huán)跌捆,我們可以把一些函數(shù)注冊(cè)到這個(gè)事件循環(huán)上徽职,當(dāng)滿足條件發(fā)生的時(shí)候,就會(huì)調(diào)用對(duì)應(yīng)的協(xié)程函數(shù)
- coroutine:協(xié)程對(duì)象佩厚,指一個(gè)使用async關(guān)鍵字定義的函數(shù)姆钉,它的調(diào)用不會(huì)立即執(zhí)行函數(shù),而是會(huì)返回一個(gè)協(xié)程對(duì)象。協(xié)程對(duì)象需要注冊(cè)到事件循環(huán)潮瓶,由事件循環(huán)調(diào)用陶冷。
- task(任務(wù)對(duì)象):一個(gè)協(xié)程對(duì)象就是一個(gè)原生可以掛起的函數(shù),任務(wù)則是對(duì)協(xié)程進(jìn)一步封裝毯辅,其中包含任務(wù)的各種狀態(tài)埂伦。
- future(未來(lái)對(duì)象):代表將來(lái)執(zhí)行或沒(méi)有執(zhí)行的任務(wù)的結(jié)果,實(shí)際上和 task 沒(méi)有本質(zhì)區(qū)別思恐。不用回調(diào)的方式沾谜,我們?cè)趺粗喇惒秸{(diào)用的結(jié)果?先設(shè)計(jì)一個(gè)對(duì)象胀莹,異步調(diào)用執(zhí)行完的時(shí)候基跑,就把結(jié)果放在它里面。這種對(duì)象稱(chēng)之為未來(lái)對(duì)象描焰。
另外我們還需要了解 async/await 關(guān)鍵字媳否,它是從 Python 3.5 才出現(xiàn)的,專(zhuān)門(mén)用于定義協(xié)程荆秦。其中篱竭,async 定義一個(gè)協(xié)程,await 用來(lái)掛起阻塞方法的執(zhí)行步绸。
定義協(xié)程
import asyncio
async def do_some_work(x):
print('Waiting: ', x)
return 'x is %d'%(x)
coroutine = do_some_work(2) #返回一個(gè)coroutine協(xié)程對(duì)象
loop = asyncio.get_event_loop() #創(chuàng)建一個(gè)事件循環(huán) loop
loop.run_until_complete(coroutine) #將協(xié)程注冊(cè)到事件循環(huán) loop 中室抽,然后啟動(dòng)
創(chuàng)建多個(gè)task
import asyncio
async def func(x):
print('waiting: ', x)
await asyncio.sleep(x) #模擬出IO阻塞
return 'x is {}'.format(x)
task_1 = asyncio.ensure_future(func(1))
task_2 = asyncio.ensure_future(func(1))
task_3 = asyncio.ensure_future(func(1))
task_4 = asyncio.ensure_future(func(1))
loop = asyncio.get_event_loop() #創(chuàng)建一個(gè)事件循環(huán) loop
tasks = [task_1, task_2, task_3, task_4]
#tasks = [asyncio.ensure_future(func(i) for i in rage(5)] #創(chuàng)建五個(gè)
loop.run_until_complete(asyncio.wait(tasks))
#將任務(wù)對(duì)象加到事件循環(huán) loop 中,然后啟動(dòng)
for task in tasks:
print(task.result())