1.gdb命令中attach使用
2.智能手机九宫格锁解锁方法大放送!智商低者慎入!
3.å¦ä½è®© Qt çç¨åºä½¿ç¨ Sleep
4.深入分析堆外内存 DirectByteBuffer & MappedByteBuffer
5.python进程数多少合适
gdb命令中attach使用
我们先看看我们的测试程序:
/* in eg1.c */
int wib(int no1, int no2)
{
int result, diff;
diff = no1 - no2;
result = no1 / diff;
return result;
}
int main()
{
pid_t pid;
pid = fork();
if (pid <0) {
printf("fork err\n");
exit(-1);
} else if (pid == 0) {
/* in child process */
sleep(); ------------------ (!)
int value = ;
int div = 6;
int total = 0;
int i = 0;
int result = 0;
for (i = 0; i < ; i++) {
result = wib(value, div);
total += result;
div++
value--;
}
printf("%d wibed by %d equals %d\n" value, div, total);
exit(0);
} else {
/* in parent process */
sleep(4);
wait(-1);
exit(0);
}
}
该测试程序中子进程运行过程中会在wib函数中出现一个‘除0‘异常。现在我们就要调试该子进程。
[调试原理]
不知道大家发现没有,在(!)处在我们的smartdoc源码解析测试程序在父进程fork后,子进程调用sleep睡了秒。这就是关键,这个sleep本来是不该存在于子进程代码中的,而是而了使用GDB调试后加入的,它是我们调试的一个关键点。为什么要让子进程刚刚运行就开始sleep呢?因为我们要在子进程睡眠期间,利用 shell命令获取其process id,然后再利用gdb调试外部进程的方法attach到该process id上,调试该进程。
[调试过程]
我觉上面的调试原理的思路已经很清晰了,剩下的就是如何操作的问题了。我们来实践一次吧!
我所使用的环境是Solaris OS 9.0/GCC 3.2/GDB 6.1。
GDB 调试程序的前提条件就是你编译程序时必须加入调试符号信息,即使用‘-g‘编译选项。首先编译我们的源程序‘gcc -g -o eg1 eg1.c‘。编译好之后,我们就有了我们的调试目标eg1。由于我们在调试过程中需要多个工具配合,所以你最好多打开几个终端窗口,另外一点需要注意的是最好在eg1的working directory下执行gdb程序,否则gdb回提示‘No symbol table is loaded‘。你还得手工load symbol table。好了,下面我们就‘按部就班‘的开始调试我们的eg1。
执行eg1:
eg1 & --- 让eg1后台运行吧。
查找进程id:
ps -fu YOUR_USER_NAME
运行gdb:
gdb
(gdb)attach xxxxx--- xxxxx为利用ps命令获得的子进程process id
(gdb)stop--- 这点很重要,你需要先暂停那个子进程,然后设置一些断点和一些Watch
(gdb)break-- 在result = wib(value, div);这行设置一个断点,可以使用list命令察看源代码
Breakpoint 1 at 0x: file eg1.c, line .
(gdb)continue
Continuing.
Breakpoint 1, main () at eg1.c:
result = wib(value, div);
(gdb)step
wib (no1=, no2=6) at eg1.c:
diff = no1 - no2;
(gdb)continue
Continuing.
Breakpoint 1, main () at eg1.c:
result = wib(value, div);
(gdb)step
wib (no1=9, no2=7) at eg1.c:
diff = no1 - no2;
(gdb)continue
Continuing.
Breakpoint 1, main () at eg1.c:
result = wib(value, div);
(gdb)step
wib (no1=8, no2=8) at eg1.c:
diff = no1 - no2;
(gdb)next
result = no1 / diff;
(gdb)print diff
$6 = 0 ------- 除数为0,我们找到罪魁祸首了。
(gdb)next
Program received signal SIGFPE, Arithmetic exception.
0xffd in .div () from /usr/lib/libc.so.1
至此,我们调试完毕。
gdb命令中attach使用
智能手机九宫格锁解锁方法大放送!智商低者慎入!
现如今,众多使用智能手机的孩子们都喜欢使用九宫格的锁屏方式防止信息泄露。也的确一些复杂的锁屏图案可以让很多人望而却步。其实这种锁屏方法解起来不是很困难,只要懂一点计算机知识就可以轻松搞定!接下来就是智能手机九宫格解锁方法大放送!高智商的孩子赶快get起来!
我们平常所用的图形锁(九宫格)是3×3的点阵,按次序连接数个点从而达到锁定/解锁的功能。最少需要连接4个点,最多能连接9个点。源码乘除法运算使用网上也有暴力删除手机图形锁的方法,即直接干掉图形锁功能。但假如你想进入别人的手机,但又不想引起其警觉的话,嘿嘿本文可以帮到你!
九宫格解锁实现前提条件:手机需要root,而且打开调试模式。一般来讲,如果用过诸如“豌豆荚手机助手”、“手机助手”一类的软件,都会被要求打开调试模式的。如果要删除手机内置软件,则需要将手机root。
九宫格解锁原理分析:
首先科普一下,安卓手机是如何标记这9个点的。通过阅读安卓系统源码可知,每个点都有其编号,组成了一个3×3的矩阵,形如:
假如设定解锁图形为一个“L”形,如图:
那么这几个点的排列顺序是这样的: 。系统就记下来了这一串数字,然后将这一串数字(以十六进制的方式)进行SHA1加密,存储在了手机里的/data/system/gesture.key 文件中。我们用数据线连接手机和电脑,然后ADB连接手机,将文件下载到电脑上(命令:adb pull /data/system/gesture.key gesture.key),如图:
用WinHex等十六进制编辑程序打开gesture.key,会发现文件内是SHA1加密过的字符串:c8c0badc8bbfdf0,如图:
当你下次解锁的时候,系统就对比你画的图案,看对应的数字串是不是对应的加密结果。如果是,就解锁;不是就继续保持锁定。那么,如果穷举所有的数字串排列,会有多少呢联想到高中的阶乘,如果用4个点做解锁图形的话,就是9x8x7x6=种可能性,那5个点就是,6个点的话,7个点,8个点,9个点。总共是种可能性(但这么计算并不严密,因为同一条直线上的点只能和他们相邻的点相连)。
满打满算,也不到种可能性。乍一看很大,但在计算机面前,穷举出来这些东西用不了几秒钟。路由打赏上网源码
破解过程
知道了原理,就着手写程序来实现吧。这里使用了Python来完成任务。主要应用了hashlib模块(对字符串进行SHA1加密)和itertools模块(Python内置,生成-的排列组合)。
主要流程为:
1.ADB连接手机,获取gesture.key文件
2.读取key文件,存入字符串str_A
3.生成全部可能的数字串
4.对这些数字串进行加密,得到字符串str_B
5.将字符串str_A与str_B进行对比
6.如果字符串A,B相同,则说明数字串num就是想要的解锁顺序
7.打印出数字串num
下面为程序:
# -*- coding: cp -*-import itertools
import hashlib
import time
import os
#调用cmd,ADB连接到手机,读取SHA1加密后的字符串
os.system("adb pull /data/system/gesture.key gesture.key")
time.sleep(5)
f=open('gesture.key','r')
pswd=f.readline()
f.close()
pswd_hex=pswd.encode('hex')print '加密后的密码为:%s'%pswd_hex
#生成解锁序列,得到['','','','','','','','','']
matrix=[]
for i in range(0,9):
str_temp = '0'+str(i)
matrix.append(str_temp)#将——的字符进行排列,至少取4个数排列,最多全部进行排列
min_num=4
max_num=len(matrix)for num in range(min_num,max_num+1):#从 -
iter1 = itertools.permutations(matrix,num)#从9个数字中挑出n个进行排列
list_m=[]
list_m.append(list(iter1))#将生成的排列全部存放到 list_m 列表中
for el in list_m[0]:#遍历这n个数字的全部排列
strlist=''.join(el)#将list转换成str。[,,,,]--
strlist_sha1 = hashlib.sha1(strlist.decode('hex')).hexdigest()#将字符串进行SHA1加密
if pswd_hex==strlist_sha1:#将手机文件里的字符串与加密字符串进行对比
print '解锁密码为:',strlist
看着是不是有点凌乱!不得不说,不懂些计算机真的想看天书一样!即便自己做不到破解别人的九宫格锁屏也没关系!至少知道懂些计算机的人可以搞定这件事。到时候忘了九宫格锁屏图案也找个行家帮忙!至少不用花钱雇人搞定。
å¦ä½è®© Qt çç¨åºä½¿ç¨ Sleep
Qt 为ä½æ²¡ææä¾ Sleep
论åä¸ä¸æ¶è§å°æ人é®ï¼
Qt 为ä»ä¹æ²¡ææä¾è·¨å¹³å°ç sleep å½æ°ï¼
使ç¨å¹³å°ç¸å ³ç Sleep æ nanosleep 以åï¼çé¢ä¸ºä»ä¹æ²¡æååºï¼
QThread ä¸æä¾äºprotected æéç sleep å½æ°ï¼å¦ä½ç¨å°ä¸»çº¿ç¨ä¸ï¼
ä½¿ç¨ QTest ä¸ç qSleepï¼å¨windowsä¸å¦ä½éèæ§å¶å°ï¼
è¿äºé®é¢å ¶å®å½ç»ä¸ºä¸ç¹ï¼å¨ä¸»çº¿ç¨ä¸ä½¿ç¨è¿äºå½æ°æ¯ä¸ç§é误ï¼è¿ä¼ç´æ¥å¯¼è´çé¢æ æ³å·æ°ï¼ç¨æ·ä¸ç¨åºæ æ³äº¤äºã
Qtä¸æä¾ï¼æ¯å ä¸ºä½ ä¸éè¦å¨ä¸»çº¿ç¨ä¸ä½¿ç¨ sleep å½æ°ã
å¦ä½è®©ç¨åºçå¾ ä¸æ®µæ¶é´
QTime
QTime t;
t.start();
while(t.elapsed()<);
è¿ç§æ»å¾ªç¯ä¹æ¯ä¸ç§å¸¸è§é误ç¨æ³ãä½æ¹ææ£ç¡®çè¿æ¯æ¯è¾ç®åçï¼
QTime t;
t.start();
while(t.elapsed()<)
QCoreApplication::processEvents();
ä¸åå°å¤çäºä»¶ï¼ä»¥ä½¿å¾ç¨åºä¿æååºã
QElapsedTimer
è¿æ¯Qt4.7å¼å ¥çæ°çç±»ï¼åQTimeç¸æ¯ï¼å®æä¾äºæ´å¿«çè®¡ç® elapsed æ¶é´çæ¹æ³ã
QElapsedTimer t;
t.start();
while(t.elapsed()<)
QCoreApplication::processEvents();
QTest::qWait
è¿æ¯QTest模åæä¾ççå¾ å½æ°
ä¸é¢æ¯å ¶æºä»£ç ï¼åæ们åé¢ç代ç å¾åå§ï¼ï¼ï¼
namespace QTest
{
inline static void qWait(int ms)
{
Q_ASSERT(QCoreApplication::instance());
QElapsedTimer timer;
timer.start();
do {
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
QTest::qSleep();
} while (timer.elapsed() < ms);
}
...
å ¶å®æ²¡ä»ä¹éå,对å§ï¼ä½æ¯å 为å®QTest模åï¼æ以å¨ç¨åºä¸æ们ä¸è¦ä½¿ç¨å®ã
QEventLoop
é åQTimer使ç¨å±é¨ç eventLoop ä¹æ¯ä¸ä¸ªä¸éçéæ©ãä¾åï¼
QEventLoop eventloop;
QTimer::singleShot(, &eventloop, SLOT(quit()));
eventloop.exec();
QTimer å QBasicTimer
è¿ä¸¤ä¸ªåæ¬æ没æä»ä¹ç´æ¥å ³ç³»ï¼QTimer估计大家é½å¾çäºãèQBasicTimer估计å¾å°æ人ç¨ã
ä¸QTimerç¸æ¯ï¼QBasicTimeræ´å¿«éãè½»éãåºå±ã
ä¸QTimerç¸æ¯ï¼å®ä¸æ¯QObjectçæ´¾çç±»ã
跨平å°çsleep
尽管ä¸å¼å§æ们就说äºï¼ä¸éè¦è¿ä¸ªä¸è¥¿ãä½ä¸æé¤æç§åºåä¸ï¼ä½ ç¡®å®éè¦è¿ä¸ªä¸è¥¿ãå¦ä½å®ç°ä¸ä¸ªè·¨å¹³å°ç sleep å¢ï¼
æ们ä¸å¼å§ä¹æå°äºï¼QThreadç±» å QTest模åé½æä¾äºsleepå½æ°ï¼å ¶å®æ们åªéè¦ççä»ä»¬çæºç å°±å¤äºï¼
QTest 模åä¸çå½æ°å¾ç®åï¼windowsä¸è°ç¨Sleepï¼å ¶ä»å¹³å°è°ç¨ nanosleepï¼ï¼
void QTest::qSleep(int ms)
{
QTEST_ASSERT(ms > 0);
#ifdef Q_OS_WIN
Sleep(uint(ms));
#else
struct timespec ts = { ms / , (ms % ) * * };
nanosleep(&ts, NULL);
#endif
}
çQThreadçæºç ï¼windowsä¸åæ ·ç´æ¥è°ç¨Sleepï¼ä½éwindowsçå®ç°æ¯è¿ä¸ªå°±å¤æå¤äºï¼
[cpp] view plain copy
/* /internal
helper function to do thread sleeps, since usleep()/nanosleep()
aren't reliable enough (in terms of behavior and availability)
*/
static void thread_sleep(struct timespec *ti)
{
pthread_mutex_t mtx;
pthread_cond_t cnd;
pthread_mutex_init(&mtx, 0);
pthread_cond_init(&cnd, 0);
pthread_mutex_lock(&mtx);
(void) pthread_cond_timedwait(&cnd, &mtx, ti);
pthread_mutex_unlock(&mtx);
pthread_cond_destroy(&cnd);
pthread_mutex_destroy(&mtx);
}
void QThread::sleep(unsigned long secs)
{
struct timeval tv;
gettimeofday(&tv, 0);
struct timespec ti;
ti.tv_sec = tv.tv_sec + secs;
ti.tv_nsec = (tv.tv_usec * );
thread_sleep(&ti);
}
深入分析堆外内存 DirectByteBuffer & MappedByteBuffer
大家好,我是大明哥,一个专注于「死磕 Java」系列创作的硬核程序员。本文内容已收录在我的技术网站:。
ByteBuffer有两种特殊类:DirectByteBuffer和MappedByteBuffer,它们的原理都是基于内存文件映射的。
ByteBuffer分为直接和间接两种。
我们先了解几个基本概念。
操作系统为什么要区分真实内存(物理内存)和虚拟内存呢?这是因为如果我们只使用物理内存会有很多问题。
对于常用的Linux操作系统而言,虚拟内存一般是4G,其中1G为系统内存,3G为应用程序内存。
进程使用的是虚拟内存,但我们数据还是存储在物理内存上,那么虚拟内存是怎样和物理内存对应起来的呢?答案是页表,虚拟内存和物理内存建立对应关系采用的是页表页映射的方式。
页表记录了虚拟内存每个页和物理内存之间的对应关系,具体如下:
它有两个栏位:有效位和路径。
当CPU寻址时,它有三种状态:
CPU访问虚拟内存地址过程如下:
下面是Linux进程的虚拟内存结构:
注意其中一块区域“Memory mapped region for shared libraries”,这块区域就是内存映射文件时将某一段虚拟地址和文件对象的某一部分建立映射关系,此时并没有拷贝数据到内存中,而是当进程代码第一次引用这段代码内的虚拟地址时,触发了缺页异常,这时候OS根据映射关系直接将文件的相关部分数据拷贝到进程的用户私有空间中去,当有操作第N页数据的时候重复这样的OS页面调度程序操作。这样就减少了文件拷贝到内核空间,江苏渔歌主图源码再拷贝到用户空间,效率比标准IO高。
接下来,我们分析MappedByteBuffer和DirectByteBuffer的类图:
MappedByteBuffer是一个抽象类,DirectByteBuffer则是它的子类。
MappedByteBuffer作为抽象类,其实它本身还是非常简单的。定义如下:
在父类Buffer中有一个非常重要的属性address,这个属性表示分配堆外内存的地址,是为了在JNI调用GetDirectBufferAddress时提升它调用的速率。这个属性我们在后面会经常用到,到时候再分析。
MappedByteBuffer作为ByteBuffer的子类,它同时也是一个抽象类,相比ByteBuffer,它新增了三个方法:
与传统IO性能对比:
相比传统IO,MappedByteBuffer只有一个字,快!!!它之所以快,在于它采用了direct buffer(内存映射)的方式来读取文件内容。这种方式是直接调动系统底层的缓存,没有JVM,少了内核空间和用户空间之间的复制操作,所以效率大大提高了。那么它相比传统IO快了多少呢?下面我们来做个小实验。
通过更改size的数字,我们可以生成k,1M,M,M,1G五个文件,我们就这两个文件来对比MappedByteBuffer和传统IO读取文件内容的性能。
大明哥电脑是GB的MacBook Pro,对k,1M,M,M,1G五个文件的测试结果如下:
绿色是传统IO读取文件的,蓝色是使用MappedByteBuffer来读取文件的,从图中我们可以看出,文件越大,两者读取速度差距越大,所以MappedByteBuffer一般适用于大文件的读取。
父类MappedByteBuffer做了基本的介绍,且与传统IO做了一个对比,这里就不对DirectByteBuffer做介绍了,咱们直接撸源码,撸了源码后我相信你对堆外内存会有更加深入的源码的动态图了解。
DirectByteBuffer是包访问级别,其定义如下:
DirectByteBuffer可以通过ByteBuffer.allocateDirect(int capacity)进行构造。
调用DirectByteBuffer构造函数:
这段代码中有三个方法非常重要:
下面就来逐个分析这三段代码。
这段代码有两个作用
maxMemory=VM.maxDirectMemory(),获取JVM允许申请的最大DirectByteBuffer的大小,该参数可通过XX:MaxDirectMemorySize来设置。这里需要注意的是-XX:MaxDirectMemorySize限制的是总cap,而不是真实的内存使用量,因为在页对齐的情况下,真实内存使用量和总cap是不同的。
tryReserveMemory()可以统计DirectByteBuffer占用总内存的大小,如果发现堆外内存无法再次分配DirectByteBuffer则会返回false,这个时候会调用jlra.tryHandlePendingReference()来进行会触发一次非堵塞的Reference#tryHandlePending(false),通过注释我们了解了该方法主要还是协助ReferenceHandler内部线程进行下一次pending的处理,内部主要是希望遇到Cleaner,然后调用Cleaner#clean()进行堆外内存的释放。
如果还不行的话那就只能调用System.gc();了,但是我们需要注意的是,调用System.gc();并不能马上就可以执行full gc,所以就有了下面的代码,下面代码的核心意思是,尝试9次,如果依然没有足够的堆外内存来进行分配的话,则会抛出异常OutOfMemoryError("Direct buffer memory")。每次尝试之前都会Thread.sleep(sleepTime),给系统足够的时间来进行full gc。
总体来说Bits.reserveMemory(size, cap)就是用来统计系统中DirectByteBuffer到底占用了多少,同时通过进行GC操作来保证有足够的内存空间来创建本次的DirectByteBuffer对象。所以对于堆外内存DirectByteBuffer我们依然可以不需要手动去释放内存,直接交给系统就可以了。还有一点需要注意的是,别设置-XX:+DisableExplicitGC,否则System.gc();就无效了。
到了这段代码我们就知道了,我们有足够的空间来创建DirectByteBuffer对象了.unsafe.allocateMemory(size)是一个native方法,它是在堆外内存(C_HEAP)中分配一块内存空间,并返回堆外内存的基地址。
这段代码其实就是创建一个Cleaner对象,该对象用于对DirectByteBuffer占用的堆外内存进行清理,调用create()来创建Cleaner对象,该对象接受两个参数:
调用Cleaner#clean()进行清理,该方法其实就是调用thunk#run(),也就是Deallocator#run():
方法很简单就是调用unsafe.freeMemory()释放指定堆外内存地址的内存空间,然后重新统计系统中DirectByteBuffer的大小情况。
Cleaner是PhantomReference的子类,PhantomReference是虚引用,熟悉JVM的小伙伴应该知道虚引用的作用是跟踪垃圾回收器收集对象的活动,当该对象被收集器回收时收到一个系统通知,所以Cleaner的作用就是能够保证JVM在回收DirectByteBuffer对象时,能够保证相对应的堆外内存也释放。
在创建DirectByteBuffer对象的时候,会new一个Cleaner对象,该对象是PhantomReference的子类,PhantomReference为虚引用,它的作用在于跟踪垃圾回收过程,并不会对对象的垃圾回收过程造成任何的影响。
当DirectByteBuffer对象从pending状态->enqueue状态,它会触发Cleaner#clean()。
在clean()方法中其实就是调用thunk.run(),该方法有DirectByteBuffer的内部类Deallocator来实现:
直接用unsafe.freeMemory()释放堆外内存了,这个address就是分配堆外内存的内存地址。
关于堆外内存DirectByteBuffer就介绍到这里,我相信小伙伴们一定有所收获。下面大明哥介绍堆内内存:HeapByteBuffer。
python进程数多少合适
导读:很多朋友问到关于python进程数多少合适的相关问题,本文首席CTO笔记就来为大家做个详细解答,供大家参考,希望对大家有所帮助!一起来看看吧!pythonmultiprocessing最大多少进程最大进程只受操作系统资源限制.
不是进程越多越好,程序的速度就越快.
一般有几个CPU核心,就开多少进程,或者核心数的N倍.
python多进程
基于官方文档:
日乐购,刚才看到的一个博客,写的都不太对,还是基于官方的比较稳妥
我就是喜欢抄官方的,哈哈
通常我们使用Process实例化一个进程,并调用他的start()方法启动它。
这种方法和Thread是一样的。
上图中,我写了p.join()所以主进程是等待子进程执行完后,才执行print("运行结束")
否则就是反过来了(这个不一定,看你的语句了,顺序其实是随机的)例如:
主进加个sleep
所以不加join(),其实子进程和主进程是各干各的,谁也不等谁。都执行完后,文件运行就结束了
上面我们用了os.getpid()和os.getppid()获取当前进程,和父进程的id
下面就讲一下,这两个函数的用法:
os.getpid()
返回当前进程的id
os.getppid()
返回父进程的id。父进程退出后,unix返回初始化进程(1)中的一个
windows返回相同的id(可能被其他进程使用了)
这也就解释了,为啥我上面的程序运行多次,第一次打印的parentid都是了。
而子进程的父级processid是调用他的那个进程的id:
视频笔记:
多进程:使用大致方法:
参考:进程通信(pipe和queue)
pool.map(函数可以有return也可以共享内存或queue)结果直接是个列表
poll.apply_async()(同map,只不过是一个进程,返回结果用xx.get()获得)
报错:
参考:
把pool=Pool()放到ifname=="main":下面初始化搞定。
结果:
这个肯定有解释的
测试多进程计算效果:
进程池运行:
结果:
普通计算:
我们同样传入三个参数测试:
其实对比下来开始快了一半的;
我们把循环里的数字去掉一个0;
单进程:
多进程:
两次测试单进程/进程池分别为0.和0.几乎成正比的。
问题二:
视图:
post视图里面
Music类:
直接报错:
写在类里面也在函数里用self.pool调用也不行,也是相同的错误。
最后把pool=Pool直接写在search函数里面,奇迹出现了:
前台也能显示搜索的音乐结果了
总结一点,进程这个东西,最好写在直接运行的函数里面,而不是一个函数跳来跳去。因为最后可能是在子进程的子进程运行的,这是不许的,会报错。
还有一点,多进程运行的函数对象,不能是lambda函数。也许lambda虚拟,在内存
使用pool.map子进程函数报错,导致整个pool挂了:
参考:
主要你要,对函数内部捕获错误,而不能让异常抛出就可以了。
关于map传多个函数参数
我一开始,就是正常思维,多个参数,搞个元祖,让参数一一对应不就行了:
报错:
参考:
普通的process当让可以穿多个参数,map却不知道咋传的。
apply_async和map一样,不知道咋传的。
最简单的方法:
使用starmap而不是map
结果:
子进程结束
1.
成功拿到结果了
关于map和starmap不同的地方看源码:
关于apply_async(),我没找到多参数的方法,大不了用一个迭代的starmap实现。哈哈
关于上面源码里面有itertools.starmap
itertools用法参考:
有个问题,多进程最好不要使用全部的cpu,因为这样可能影响其他任务,所以在进程池添加process参数指定,cpu个数:
上面就是预留了一个cpu干其他事的
后面直接使用Queue遇到这个问题:
解决:
Manager().Queue()代替Queue()
因为queue.get()是堵塞型的,所以可以提前判断是不是空的,以免堵塞进程。比如下面这样:
使用queue.empty()空为True
python并发编程-进程池在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。多进程是实现并发的手段之一,需要注意的问题是:
例如当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数..
ps:对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。
创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程
参数介绍:
方法介绍:
主要方法:
其他方法(了解部分)
应用:
发现:并发开启多个客户端,服务端同一时间只有3个不同的pid,干掉一个客户端,另外一个客户端才会进来,被3个进程之一处理
回调函数:
需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数
我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果。
如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数
python单个进程最大连接数python单个进程最大连接数默认为为。socket连接数的理论值应该和一个进程所能打开的最大文件描述符数相等。
python多进程为什么一定要前面讲了为什么Python里推荐用多进程而不是多线程,但是多进程也有其自己的限制:相比线程更加笨重、切换耗时更长,并且在python的多进程下,进程数量不推荐超过CPU核心数(一个进程只有一个GIL,所以一个进程只能跑满一个CPU),因为一个进程占用一个CPU时能充分利用机器的性能,但是进程多了就会出现频繁的进程切换,反而得不偿失。
不过特殊情况(特指IO密集型任务)下,多线程是比多进程好用的。
举个例子:给你W条url,需要你把每个url对应的页面抓取保存起来,这种时候,单单使用多进程,效果肯定是很差的。为什么呢看
例如每次请求的等待时间是2秒,那么如下(忽略cpu计算时间):
1、单进程+单线程:需要2秒*W=W秒==.个小时==.3天,这个速度明显是不能接受的2、单进程+多线程:例如我们在这个进程中开了个多线程,比1中能够提升倍速度,也就是大约4.天能够完成W条抓取,请注意,这里的实际执行是:线程1遇见了阻塞,CPU切换到线程2去执行,遇见阻塞又切换到线程3等等,个线程都阻塞后,这个进程就阻塞了,而直到某个线程阻塞完成后,这个进程才能继续执行,所以速度上提升大约能到倍(这里忽略了线程切换带来的开销,实际上的提升应该是不能达到倍的),但是需要考虑的是线程的切换也是有开销的,所以不能无限的启动多线程(开W个线程肯定是不靠谱的)3、多进程+多线程:这里就厉害了,一般来说也有很多人用这个方法,多进程下,每个进程都能占一个cpu,而多线程从一定程度上绕过了阻塞的等待,所以比单进程下的多线程又更好使了,例如我们开个进程,每个进程里开W个线程,执行的速度理论上是比单进程开W个线程快倍以上的(为什么是倍以上而不是倍,主要是cpu切换W个线程的消耗肯定比切换W个进程大得多,考虑到这部分开销,所以是倍以上)。
还有更好的方法吗看答案是肯定的,它就是:
4、协程,使用它之前我们先讲讲what/why/how(它是什么/为什么用它/怎么使用它)what:
协程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。
why:
目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptivemultitasking),而与协程相关的是协作式多任务。
不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(systemcall),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。
而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。
因为协程是用户自己来编写调度逻辑的,对CPU来说,协程其实是单线程,所以CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程。
how:
python里面怎么使用协程看答案是使用gevent,使用方法:看这里使用协程,可以不受线程开销的限制,我尝试过一次把W条url放在单进程的协程里执行,完全没问题。
所以最推荐的方法,是多进程+协程(可以看作是每个进程里都是单线程,而这个单线程是协程化的)多进程+协程下,避开了CPU切换的开销,又能把多个CPU充分利用起来,这种方式对于数据量较大的爬虫还有文件读写之类的效率提升是巨大的。
小例子:
#-*-coding=utf-8-*-
importrequests
frommultiprocessingimportProcess
importgevent
fromgeventimportmonkey;monkey.patch_all()importsys
reload(sys)
sys.setdefaultencoding('utf8')
deffetch(url):
try:
s=requests.Session()
r=s.get(url,timeout=1)#在这里抓取页面
exceptException,e:
printe
return''
defprocess_start(tasks):
gevent.joinall(tasks)#使用协程来执行
deftask_start(filepath,flag=):#每W条url启动一个进程withopen(filepath,'r')asreader:#从给定的文件中读取urlurl=reader.readline().strip()
task_list=[]#这个list用于存放协程任务
i=0#计数器,记录添加了多少个url到协程队列whileurl!='':
i+=1
task_list.append(gevent.spawn(fetch,url,queue))#每次读取出url,将任务添加到协程队列ifi==flag:#一定数量的url就启动一个进程并执行p=Process(target=process_start,args=(task_list,))p.start()
task_list=[]#重置协程队列
i=0#重置计数器
url=reader.readline().strip()
iftask_listnot[]:#若退出循环后任务队列里还有url剩余p=Process(target=process_start,args=(task_list,))#把剩余的url全都放到最后这个进程来执行p.start()
if__name__=='__main__':
task_start('./testData.txt')#读取指定文件细心的同学会发现:上面的例子中隐藏了一个问题:进程的数量会随着url数量的增加而不断增加,我们在这里不使用进程池multiprocessing.Pool来控制进程数量的原因是multiprocessing.Pool和gevent有冲突不能同时使用,但是有兴趣的同学可以研究一下gevent.pool这个协程池。
另外还有一个问题:每个进程处理的url是累积的而不是独立的,例如第一个进程会处理W个,第二个进程会变成W个,以此类推。最后定位到问题是gevent.joinall()导致的问题,有兴趣的同学可以研究一下为什么会这样。不过这个问题的处理方案是:主进程只负责读取url然后写入到list中,在创建子进程的时候直接把list传给子进程,由子进程自己去构建协程。这样就不会出现累加的问题
python进程池最大数量初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。
结语:以上就是首席CTO笔记为大家介绍的关于python进程数多少合适的全部内容了,希望对大家有所帮助,如果你还想了解更多这方面的信息,记得收藏关注本站。