Python 多线程、多进程最全整理

python3多线程示例

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, delay):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.delay = delay
    def run(self):
        print ("开启线程: " + self.name)
        # 获取锁,用于线程同步
        threadLock.acquire()
        print_time(self.name, self.delay, 3)
        # 释放锁,开启下一个线程
        threadLock.release()

def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

threadLock = threading.Lock()
threads = []

# 创建新线程
print("创建新线程")
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启新线程
print("开启新线程")
thread1.start()
thread2.start()

# 添加线程到线程列表
print("添加线程到线程列表")
threads.append(thread1)
threads.append(thread2)

# 等待所有线程完成
for t in threads:
    t.join()

print ("退出主线程")

示例2,多线程爬虫

import threading
import time
from urllib.request import urlopen
def timeit(f):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = f(*args, **kwargs)
        end_time = time.time()
        print("%s函数运行时间:%.2f" % (f.__name__, end_time - start_time))
        return res
    return wrapper
class MyThread(threading.Thread):
    def __init__(self, ip):
        super(MyThread, self).__init__()
        self.ip = ip
    def run(self):
        url = "http://ip-api.com/json/%s" % (self.ip)
        urlObj = urlopen(url)
        # 服务端返回的页面信息, 此处为字符串类型
        pageContent = urlObj.read().decode('utf-8')
        # 2. 处理Json数据
        import json
        # 解码: 将json数据格式解码为python可以识别的对象;
        dict_data = json.loads(pageContent)
        print("""
                            %s
        所在城市: %s
        所在国家: %s

        """ % (self.ip, dict_data['city'], dict_data['country']))
@timeit
def main():
    ips = ['12.13.14.%s' % (i + 1) for i in range(10)]
    threads = []
    for ip in ips:
        # 实例化自己重写的类
        t = MyThread(ip)
        threads.append(t)
        t.start()
    [thread.join() for thread in threads]
if __name__ == '__main__':
    main()

示例3:
python多线程 + 批量插入 数据库 健壮你的小爬虫
https://zhuanlan.zhihu.com/p/29318205

# -*- coding: UTF-8 -*-
import requests
from lxml import etree
import csv
import MySQLdb
import xlwt

import Queue
import threading
import time

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

# from util.crawler import Header, Proxy  代理 请求头 我放在另个文件夹
# from database.db import Database
#
# con = Database.getConnection()   # 连接数据库
# cur = con.cursor()   # 游标对象

def get_news_url(start_url, result_queue):
    result = []
    while start_url.qsize():

        page_url = start_url.get() # 从队列中移除并返回这个数据
        try:
            response = requests.get(page_url)
        except Exception as e:
            print "抓取网页错误,错误为:%s" % e
            return None

        if response.status_code == 200:
            selector = etree.HTML(response.text)
            web_content = selector.xpath('//p[@class="tit"]')
            for news in web_content:
                item_result = {}
                item_result['href'] = news.xpath('a/@href')[0]
                item_result['title'] = news.xpath('a/text()')[0]
                item_result['date_news'] = news.xpath('span/text()')[0]
                result.append(item_result)
            if len(result) > 0:
                result_queue.put(result) # put是向结果集 队列里添加元素
                start_url.task_done()
        else:
            time.sleep(5)

def save_to_excel(result):
    workbook = xlwt.Workbook()
    sheet = workbook.add_sheet('result3')
    title = ['href','title','date']
    for i ,item in enumerate(title):
        sheet.write(0, i, item)
    data = [item.values() for item in result]
    print data
    for row, item in enumerate(data):
        for i, info in enumerate(item):
            print row+1, i ,info
            sheet.write(row+1 , i , info)
    workbook.save('Myresult.xls')

def save_news_mysql(result):

    con = MySQLdb.connect(host= '127.0.0.1', user= 'root', passwd= '123456' ,charset='utf8',port = 3306)
    cur = con.cursor()
    sql = 'create database if not exists cstn_database default charset utf8'
    cur.execute(sql)
    con.select_db('cstn_database')
    sql = 'create table if not exists news_cstn'+"(id int auto_increment, href varchar(255), title varchar(255), \
    date_news varchar(255), primary key(ID))"
    cur.execute(sql) # 创建表结构 这部分代码以后直接在mysql里创建就可以了
    sql2 = 'insert into news_cstn  (href,title,date_news)  VALUES (%s,%s,%s)'
    data = [item.values() for item in result]
    cur.executemany(sql2, tuple(data))
    print 'insert sucessful'

    # for elem in result: # 单条插入
    #     sql = 'insert into news_cstn  (href,title,date_news)  VALUES (\'%s\',\'%s\',\'%s\')' % (elem['href'],elem['title'],elem['date_news'])
    #     cur.execute(sql)

    con.commit()
    cur.close()
    con.close()

def main():
    start_url = Queue.Queue()  # 存放url的队列
    result_queue = Queue.Queue() # 结果集队列
    for i in range(1, 3):
        page_url = 'http://data.stcn.com/list/djsj_%s.shtml' % i
        start_url.put(page_url)  # 将值插入队列中
    # 构建线程
    thread_list = []
    for n in range(4):  # 创建4 个线程
        t_t = threading.Thread(target=get_news_url, args=(start_url, result_queue))  # 创建线程,调用get_news_url方法,args传入参数
        thread_list.append(t_t)
    for t in thread_list:
        t.start()
    start_url.join() # 就是当所有的url全部获取完,放入到结果集里才开始存入数据库,防止出现 插入数据库报错的情况
    while result_queue.qsize(): # 返回队列的大小
         save_news_mysql(result_queue.get()) # 将结果存入数据库中

if __name__ == "__main__":
    main()

多进程和多线程的比较

总结,进程和线程还可以类比为火车和车厢:

线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到该趟火车的所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-”互斥锁(mutex)”
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量(semaphore)”

使用多线程还是多进程?

了解到 Python 多线程的问题和解决方案,对于钟爱 Python 的我们,何去何从呢?

有句话用在这里很合适:

求人不如求己

哪怕再怎么厉害的工具或者武器,都无法解决所有的问题,而问题之所以能被解决,主要是因为我们的主观能动性。

对情况进行分析判断,选择合适的解决方案,不就是需要我们做的么?

对于 Python 中 多线程的诟病,我们更多的是看到它阳光和美的一面,而对于需要提升速度的地方,采取合适的方式。这里简单总结一下:

1、I/O 密集型的任务,采用 Python 的多线程完全没用问题,可以大幅度提高执行效率;
2、对于计算密集型任务,要看数据依赖性是否低,如果低,采用 ProcessPoolExecutor 代替多线程处理,可以充分利用硬件资源
3、如果数据依赖性高,可以考虑将关键的地方该用 C 来实现,一方面 C 本身比 Python 更快,另一方面,C 可以之间使用更底层的多线程机制,而完全不用担心受 GIL 的影响
大部分情况下,对于只能用多线程处理的任务,不用太多考虑,之间利用 Python 的多线程机制就好了,不用考虑太多

总结

没用十全十美的解决方案,如果有,也只能是在某个具体的条件之下,就像软件工程中,没用银弹一样。

面对真实的世界,只有我们自己是可以依靠的,我们通过学习了解更多,通过实践,感受更多,通过总结复盘,收获更多,通过思考反思,解决更多。这就是我们人类不断发展前行的原动力。


相关文章:
Python3 多线程
Python多线程、多进程最全整理
python多线程是假的!速度慢!效率低下!不升反降!
知乎 | Python 多线程居然是——假的?
python之多线程
python 实现多线程的三种方法总结

为者常成,行者常至