python 主线程读取数据写入队列,多线程消费队列数据

该博客介绍了如何使用Python进行多线程数据处理,通过主线程读取数据并放入队列,多个消费者线程从队列中取出数据,执行清理操作。清理操作涉及查询数据库、删除符合条件的记录,并使用了`dbutils.pooled_db`模块来管理数据库连接池,确保了在多线程环境下的安全性。此外,还展示了如何记录日志和处理异常。
摘要由CSDN通过智能技术生成

备忘记录
主线程读取数据写入队列,多线程消费队列数据。

# -*- coding: UTF-8 -*-
# !/usr/bin/python3

import logging
import queue
import threading
import time

import pymysql
from dbutils.pooled_db import PooledDB

###############参数配置######################
host = "host"
port = 3306
db = "db"
user = "user"
passwd = "passwd"
# 连接池最小数
mincached = 1
# 连接池最大数
maxcached = 20
# 并发执行线程数
threads = 1
# 睡眠时间-秒
sleepSecend = 0
############################################

logging.basicConfig(filename="log.log", level=logging.INFO,
                    format="%(asctime)s: %(thread)d - %(levelname)s - %(message)s",
                    datefmt="%Y-%m-%d %H:%M:%S")

lock = threading.Lock()

job_begin = time.time() * 1000

def clearData(data):
    deleteIds = []

    try:
        querySql = "querySql2"

        param01 = bytes.decode(data['param01'])
        param02 = bytes.decode(data['param02'])

        param = (param01 + "," + param02).split(",")
        # 加锁 防止多线程模式下错误的释放了其他线程的连接,导致的AttributeError: 'NoneType' object has no attribute 'read'
        lock.acquire()
        rr = mysql.getAll(querySql, param)

        for r in rr:
            if r["delete"] == 1:
                deleteIds.append(bytes.decode(r['id']))

        if len(deleteIds) != 0:
            # 批量删除id 数组适配为'id1','id2','id3'格式
            deleteSql = "delete from table where id in (%s) " % ','.join(
                ['%s'] * len(deleteIds))
            mysql.delete(deleteSql, deleteIds)
            logging.info("delete sql :" + deleteSql + ",".join(deleteIds))

    except Exception as e:
        logging.error("clearData catch exception:" + str(e))
    finally:
        # 释放锁
        lock.release()
        # 手动提交事务
        mysql.end()


def consumer(threadNum):
    while True:
        if q.empty():
            logging.info(
                "[thread-" + str(threadNum) + "]queue is empty. cost: " + str(time.time() * 1000 - job_begin) + "ms")
            return
        else:
            data = q.get()
            logging.info("[thread-" + str(threadNum) + "]from queue got one record:" + str(data) + ".left size:" + str(
                q.qsize()))
            clearData(data)
            time.sleep(sleepSecend)


class Mysql(object):
    """
    MYSQL数据库对象,负责产生数据库连接 , 此类中的连接采用连接池实现获取连接对象:conn = Mysql.getConn()
            释放连接对象;conn.close()或del conn
    """
    # 连接池对象
    __pool = None

    def __init__(self):
        # 数据库构造函数,从连接池中取出连接,并生成操作游标
        self._conn = Mysql.__getConn()
        self._cursor = self._conn.cursor()

    @staticmethod
    def __getConn():
        """
        @summary: 静态方法,从连接池中取出连接
        @return MySQLdb.connection
        """
        if Mysql.__pool is None:
            __pool = PooledDB(creator=pymysql, mincached=mincached, maxcached=maxcached,
                              host=host, port=port, user=user, passwd=passwd,
                              db=db, use_unicode=False, charset="utf8",
                              cursorclass=pymysql.cursors.DictCursor)
        return __pool.connection()

    def getAll(self, sql, param=None):
        """
        @summary: 执行查询,并取出所有结果集
        @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
        @param param: 可选参数,条件列表值(元组/列表)
        @return: result list(字典对象)/boolean 查询到的结果集
        """
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql, param)
        if count > 0:
            result = self._cursor.fetchall()
        else:
            result = False
        return result

    def getOne(self, sql, param=None):
        """
        @summary: 执行查询,并取出第一条
        @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
        @param param: 可选参数,条件列表值(元组/列表)
        @return: result list/boolean 查询到的结果集
        """
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql, param)
        if count > 0:
            result = self._cursor.fetchone()
        else:
            result = False
        return result

    def getMany(self, sql, num, param=None):
        """
        @summary: 执行查询,并取出num条结果
        @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
        @param num:取得的结果条数
        @param param: 可选参数,条件列表值(元组/列表)
        @return: result list/boolean 查询到的结果集
        """
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql, param)
        if count > 0:
            result = self._cursor.fetchmany(num)
        else:
            result = False
        return result

    def insertOne(self, sql, value):
        """
        @summary: 向数据表插入一条记录
        @param sql:要插入的SQL格式
        @param value:要插入的记录数据tuple/list
        @return: insertId 受影响的行数
        """
        self._cursor.execute(sql, value)
        return self.__getInsertId()

    def insertMany(self, sql, values):
        """
        @summary: 向数据表插入多条记录
        @param sql:要插入的SQL格式
        @param values:要插入的记录数据tuple(tuple)/list[list]
        @return: count 受影响的行数
        """
        count = self._cursor.executemany(sql, values)
        return count

    def __getInsertId(self):
        """
        获取当前连接最后一次插入操作生成的id,如果没有则为0
        """
        self._cursor.execute("SELECT @@IDENTITY AS id")
        result = self._cursor.fetchall()
        return result[0]['id']

    def __query(self, sql, param=None):
        if param is None:
            count = self._cursor.execute(sql)
        else:
            count = self._cursor.execute(sql, param)
        return count

    def update(self, sql, param=None):
        """
        @summary: 更新数据表记录
        @param sql: SQL格式及条件,使用(%s,%s)
        @param param: 要更新的  值 tuple/list
        @return: count 受影响的行数
        """
        return self.__query(sql, param)

    def delete(self, sql, param=None):
        """
        @summary: 删除数据表记录
        @param sql: SQL格式及条件,使用(%s,%s)
        @param param: 要删除的条件 值 tuple/list
        @return: count 受影响的行数
        """
        return self.__query(sql, param)

    def begin(self):
        """
        @summary: 开启事务
        """
        self._conn.autocommit(0)

    def end(self, option='commit'):
        """
        @summary: 结束事务
        """
        if option == 'commit':
            self._conn.commit()
        else:
            self._conn.rollback()

    def dispose(self, isEnd=1):
        """
        @summary: 释放连接池资源
        """
        if isEnd == 1:
            self.end('commit')
        else:
            self.end('rollback');
        self._cursor.close()
        self._conn.close()


if __name__ == "__main__":
    q = queue.Queue()
    mysql = Mysql()

    sql = "querySql"
    try:
        start = time.time() * 1000
        result = mysql.getAll(sql)
        for col in result:
            q.put(col)
        logging.info("log: " + str(time.time() * 1000 - start) + "ms")

    except Exception as e:
        logging.error("failed exception: " + str(e))

    try:
        for i in range(threads):
            threading.Thread(target=consumer, args=(i,)).start()

    except Exception as e:
        logging.error("threading crate exception: " + str(e))

参考资料:
https://docs.python.org/3/library/queue.html
https://www.cnblogs.com/hi3254014978/p/15084903.html
https://webwareforpython.github.io/DBUtils/main.html
https://github.com/WebwareForPython/DBUtils
https://www.jianshu.com/p/e30d302ebdeb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值