本文希望达到的目标:

1、服务器端与线程池  (实例demo)

2、并发插入db与线程池(实例demo)

3、线程池使用说明

4、线程池源码解析

一、基于socket的服务器与线程池连接。

1、在i7内核,windows机器下,处理300笔客户端发的请求(客户端开启3个进程,每个进程串行发送数据100笔),为模拟服务器处理过程,服务器在返回数据前,服务器休眠0.5s(如果没有的话,线程池和单进程是一样的效果),使用基于线程池(同时存在10个线程)构建的服务器端,耗时为50s,随时创建随时销毁的服务器端,耗时为140s。

这个对比不能说明很多问题,因为不够真实,一方面客户端的并发量没上去;另一方面服务器端的应答时间比较随意,如果服务器端不sleep 0.5s,导致的结果是两种写法的耗用时间会是差不多的,因为创建单个线程的时间不是很长。但是结果差距的明显,说明了线程池在场景下还是有一定的性能优势的(开启10个进程,每个进程发送数据100笔,基于线程池的服务器也能在50s内处理完成)。

import  thread

import time, threading
import socket host =''
port =8888
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((host,port))
s.listen(3) class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args def run(self):
self.func(*self.args) def handle_request(conn_socket):
recv_data = conn_socket.recv(1024)
reply = 'HTTP/1.1 200 OK \r\n\r\n'
reply += 'hello world'
time.sleep(0.5)
conn_socket.send(reply)
conn_socket.close() def main():
while True:
conn_socket, addr = s.accept()
t = MyThread(handle_request,(conn_socket,))
t.start()
t.join() if __name__ == '__main__':
main()

还有网上重写的线程池,如果需要使用线程池做真正的项目,个人不建议参考网上这些不规范的脚本:

#!/usr/bin/env python
#coding=gbk import socket
from Queue import Queue
from threading import Thread
import threading """
重写threadpool,自定义和实现线程池,实现web服务端多线程处理
"""
host =''
port =
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((host,port))
s.listen() class ThreadPoolManger():
def __init__(self,thread_num):
self.work_queue =Queue()
self.thread_num = thread_num
self._init_threading_pool(self.thread_num) def _init_threading_pool(self,thread_num):
for i in range(thread_num):
thread= ThreadManger(self.work_queue)
thread.start() def add_job(self,func,*args):
self.work_queue.put((func,args)) class ThreadManger(Thread):
def __init__(self,work_queue):
Thread.__init__(self)
self.work_queue = work_queue
self.daemon =True def run(self):
while True:
target,args = self.work_queue.get()
target(*args)
self.work_queue.task_done() def handle_request(conn_socket):
recv_data = conn_socket.recv()
reply = 'HTTP/1.1 200 OK \r\n\r\n'
reply += 'hello world'
print 'thread %s is running ' % threading.current_thread().name
conn_socket.send(reply)
conn_socket.close() thread_pool = ThreadPoolManger()
while True:
conn_socket,addr =s.accept()
thread_pool.add_job(handle_request,*(conn_socket,)) s.close()

二、向DB插入数据1w条

不考虑插入数据时的一些技巧(比如拼接sql批量插入;或者先写文件,再把调用mysql命令把文件导入db),单纯的基于多线程和线程池的差异对比,结果是同时开启10个线程插入1w笔数据耗时为32s,而使用线程池(size=10)耗时为9s,两者DB的连接

都是每个线程有自己的句柄,内部实现是一致的,这个结果充分的说明了线程池的优势。

#!/usr/bin/env python
#coding=GBK
from mytool.mysql import MySql
import threading
import time class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args def run(self):
self.func(*self.args) def make_t_tcpay_list(Flistid,Ftde_id,n):
Fnum =0.5
Fabankid =
Fuid =
wbdb_mysql = MySql("10.125.56.154", "root", "", "wd_db")
for i in range(n):
sql ="**" //自定义完成
wbdb_mysql.connect()
wbdb_mysql.execute_insert_command(sql)
Flistid = Flistid +
Ftde_id = Ftde_id + if __name__ == "__main__": start = time.time()
Threads=[]
Flistid =
Ftde_id = 1 for i in range():
print i
Flistid = Flistid+
Ftde_id = Ftde_id +
t = MyThread(make_t_tcpay_list, (Flistid,Ftde_id,))
Threads.append(t) for i in range():
Threads[i].start() for i in range():
Threads[i].join() end = time.time()
print end - start

下面是线程池:

#!/usr/bin/env python
#coding=GBK
from mytool.mysql import MySql
import threadpool
import time def make_t_tcpay_list(n,Flistid,Ftde_id):
Fnum =0.5
Fabankid =
Fuid =
wbdb_mysql = MySql("10.125.56.154", "root", "root1234", "wd_db")
wbdb_mysql.connect() for i in range(int(n)):
sql ="" //自定义完成
wbdb_mysql.execute_insert_command(sql)
Flistid =int(Flistid) +
Ftde_id =Ftde_id + if __name__ == "__main__": start = time.time()
lst_vars_1 = ['','',]
lst_vars_2 = ['','',]
lst_vars_3 = ['','', ]
lst_vars_4 = ['','', ]
lst_vars_5 = ['','', ]
lst_vars_6 = ['','', ]
lst_vars_7 = ['', '', ]
lst_vars_8 = ['', '', ]
lst_vars_9 = ['', '', ]
lst_vars_10 = ['', '', ] func_var = [(lst_vars_1, None), (lst_vars_2, None), (lst_vars_3, None), (lst_vars_4, None), (lst_vars_5, None), (lst_vars_6, None), (lst_vars_7, None), (lst_vars_8, None), (lst_vars_9, None), (lst_vars_10, None)] pool = threadpool.ThreadPool()
requests = threadpool.makeRequests(make_t_tcpay_list, func_var)
for req in requests:
pool.putRequest(req)
pool.wait()
end = time.time()
print end - start

三、线程池使用说明

线程池基本原理: 我们把任务放进队列中去,然后开N个线程,每个线程都去队列中取一个任务,执行完了之后告诉系统说我执行完了,然后接着去队列中取下一个任务,直至队列中所有任务取空,退出线程。

通过上面2个demo,对单线程,多线程,线程池实际处理的差异性有了一定的了解。但是实际因为Cpython的GIL全局排他锁的存在,导致任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁。多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

对于IO密集型的任务,多线程还是起到很大效率提升(之前的两个demo都属于IO密集型),因为进行耗时的IO操作的时候,会能释放GIL,从而其他线程就可以获取GIL,提供了任务的并发处理能力。比如,计算时间占20%, IO等待时间80%,假设任务完成需要5s,可以想象成完成任务过程:CPU占用1秒,等待时间4秒,CPU在线程等待时,可以切换另外一个线程进程,这个线程的CPU运行完了,还能允许再激活3个线程,这个是第一个线程等待时间结束了,CPU切换回去,第一个线程任务就处理完成了,完成5个任务,只需要10s。这里还有一个公式: 线程数= N*(x+y)/x;N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,这样能让CPU的利用率最大化。 由于有GIL的影响,python只能使用到1个核,所以这里设置N=1

而对于计算密集型的任务,先看一个简单的demo,计算100次(1+2+...+10000),单进程耗时0.44s,而多进程和线程池耗时呢?比单进程还差,耗时1.2s。

单进程:

import time, threading

def change_it(n):
for j in range(n):
sum =
for i in range():
sum = sum + i if __name__ == "__main__":
start = time.time()
change_it()
end = time.time()
print end - start

耗时:

线程池(多进程):

#coding=GBK
from mytool.mysql import MySql
import threadpool
import time def change_it(n):
for j in range(n):
sum =
for i in range():
sum = sum + i if __name__ == "__main__": start = time.time() func_var = [,,,,,,,,,] pool = threadpool.ThreadPool()
requests = threadpool.makeRequests(change_it, func_var)
for req in requests:
pool.putRequest(req)
pool.wait()
end = time.time()
print end - start

耗时:

看到python在多线程的情况下居然比单线程整整慢了61%。对于由于一个CPU密集型的任务,使用多线程编程,会因为CPU一直处于运行状态,而线程又要等待获取GIL锁,从而进行线程处于循环等待状态,导致性能反而下降。

四、线程池源码解析

1、核心类:ThreadPool

类成员变量:

class ThreadPool:
def __init__(self, num_workers, q_size=):
self.requestsQueue = Queue.Queue(q_size)
self.resultsQueue = Queue.Queue()
self.workers = []
self.workRequests = {}
self.createWorkers(num_workers)

用户自定义的类似,不同的地方多了一个结果队列,把创建的运行线程全都放入了workers 内,且把所有的任务对象化workRequests

关键函数:

    def createWorkers(self, num_workers):
"""Add num_workers worker threads to the pool.""" for i in range(num_workers):
self.workers.append(WorkerThread(self.requestsQueue,
self.resultsQueue))
    def putRequest(self, request, block=True, timeout=):
"""Put work request into work queue and save its id for later.""" assert isinstance(request, WorkRequest)
self.requestsQueue.put(request, block, timeout)
self.workRequests[request.requestID] = request

2、工作线程类 :WorkerThread

成员变量:

class WorkerThread(threading.Thread):

    def __init__(self, requestsQueue, resultsQueue, **kwds):

        threading.Thread.__init__(self, **kwds)
self.setDaemon()
self.workRequestQueue = requestsQueue
self.resultQueue = resultsQueue
self._dismissed = threading.Event()
self.start()

关键函数:

    def run(self):

        while not self._dismissed.isSet():
# thread blocks here, if queue empty
request = self.workRequestQueue.get()
if self._dismissed.isSet():
# if told to exit, return the work request we just picked up
self.workRequestQueue.put(request)
break # and exit
try:
self.resultQueue.put(
(request, request.callable(*request.args, **request.kwds))
)
except:
request.exception = True
self.resultQueue.put((request, sys.exc_info()))

3、生成任务对象

def makeRequests(callable, args_list, callback=None, exc_callback=None):

    requests = []
for item in args_list:
if isinstance(item, tuple):
requests.append(
WorkRequest(callable, item[], item[], callback=callback,
exc_callback=exc_callback)
)
else:
requests.append(
WorkRequest(callable, [item], None, callback=callback,
exc_callback=exc_callback)
)
return requests

其中任务对象类定义为:

class WorkRequest:
if requestID is None:
self.requestID = id(self)
else:
try:
hash(requestID)
except TypeError:
raise TypeError("requestID must be hashable.")
self.requestID = requestID
self.exception = False
self.callback = callback
self.exc_callback = exc_callback
self.callable = callable
self.args = args or []
self.kwds = kwds or {}
												

python多线程学习三的更多相关文章

  1. Python基础学习三

    Python基础学习三 1.列表与元组 len()函数:可以获取列表的元素个数. append()函数:用于在列表的最后添加元素. sort()函数:用于排序元素 insert()函数:用于在指定位置 ...

  2. python 多线程学习小记

    python对于thread的管理中有两个函数:join和setDaemon setDaemon:如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.set ...

  3. python多线程学习(一)

    python多线程.多进程 初探 原先刚学Java的时候,多线程也学了几天,后来一直没用到.然后接触python的多线程的时候,貌似看到一句"python多线程很鸡肋",于是乎直接 ...

  4. python多线程学习记录

    1.多线程的创建 import threading t = t.theading.Thread(target, args--) t.SetDeamon(True)//设置为守护进程 t.start() ...

  5. python多线程学习二

    本文希望达到的目标: 多线程同步原语:互斥锁 多线程队列queue 线程池threadpool 一.多线程同步原语:互斥锁 在多线程代码中,总有一些特定的函数或者代码块不应该被多个线程同时执行,通常包 ...

  6. python 多线程学习

    多线程(multithreaded,MT),是指从软件或者硬件上实现多个线程并发执行的技术 什么是进程? 计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据.它们只有在被读取到内存中,被操作系 ...

  7. python多线程学习一

    本文希望达到的目标: 多线程的基本认识 多线程编程的模块和类的使用 Cpython的全局解释器锁GIL 一.多线程的基本认识 多线程编程的目的:并行处理子任务,大幅度地提升整个任务的效率. 线程就是运 ...

  8. Python多线程笔记(三),queue模块

    尽管在Python中可以使用各种锁和同步原语的组合编写非常传统的多线程程序,但有一种首推的编程方式要优于其他所有编程方式即将多线程程序组织为多个独立人物的集合,这些任务之间通过消息队列进行通信 que ...

  9. JAVA多线程学习- 三:volatile关键字

    Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...

随机推荐

  1. laravel项目安装

    composer install 遇到的问题 composer.json 里有多余的逗号 没有打开php_openssl.php_fileinfo 报错没具体报错信息,修改配置文件 config\ap ...

  2. nginx通过反向代理实现未备案域名访问详解

    本方法实现前提是8123端口(也可以是其他端口)面对互联网打开.server里面监听80端口,然后反向代理8123端口.1.其中server_name部分是我的域名可以替换成其他想要的域名2.8123 ...

  3. go关键字之select

    select是Go中的一个控制结构,类似于switch语句,用于处理异步IO操作.select会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时 ...

  4. JS开发中常用的小技巧

    1.获取指定范围内的随机数 1 2 3 function getRadomNum(min,max){     return  Math.floor(Math.random() * (max - min ...

  5. CentOS7安装Go环境

    下载go(我的当前目录是/data/work)$wget https://studygolang.com/dl/golang/go1.10.1.linux-amd64.tar.gz$tar -xvf ...

  6. XML5个转义符:<,>,&,”,©;的转义字符分别如下: &lt; &gt;&amp; &quot; &apos;

    XML5个转义符:<,>,&,”,©;的转义字符分别如下: < >& " &apos;             $search = array ...

  7. 自闭的D7

    先是一道**题然后我死了啊. A: 哇人人都会我不会系列. 我们可以运用逆向思维啊,把它转化成若干个人从点1进来, 然后我们考虑深度 dep,用 num[dep] 表示深度大于等于这个点的有多少个, ...

  8. Linux 的基本操作(系统用户及用户组的管理)

    [认识/etc/passwd和/etc/shadow] 这两个文件可以说是linux系统中最重要的文件之一.如果没有这两个文件或者这两个文件出问题,则你是无法正常登录linux系统的. /etc/pa ...

  9. mac homebrew

    brew 全称Homebrew  是Mac OSX上的软件包管理工具 Homebrew 安装和卸载工具 只用一行命令就能完成 官方地址:    http://brew.sh/index.html   ...

  10. sublime插件之px转rem

    sublime插件之px转rem   安装: 1.下载sublime并安装: 2.下载cssrem:https://github.com/hyb628/cssrem: 3.找到 Packages (首 ...