线程同步、queue和celery

一、线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下:

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。

考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。

那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。

经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time    : 2018/5/28 0:20
# @Author  : zhouyuyao
# @File    : demon1.py
import threading
import time

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

def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("{0}:{1}".format(threadName, time.ctime(time.time())))
        counter -= 1

threadLock = threading.Lock()
threads = []

# 创建新线程
thread1 = myThread(1, "Thread-1", 1)        # 实例化类
thread2 = myThread(2, "Thread-2", 2)

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

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

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")

运行结果如下

开启线程: Thread-1
开启线程: Thread-2
Thread-1:Mon May 28 00:27:03 2018
Thread-1:Mon May 28 00:27:04 2018
Thread-1:Mon May 28 00:27:05 2018
Thread-2:Mon May 28 00:27:07 2018
Thread-2:Mon May 28 00:27:09 2018
Thread-2:Mon May 28 00:27:11 2018
退出主线程

二、线程优先级队列( Queue)

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。

这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。

Queue 模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time    : 2018/5/28 1:00
# @Author  : zhouyuyao
# @File    : demon2.py
import queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print ("开启线程:" + self.name)
        process_data(self.name, self.q)
        print ("退出线程:" + self.name)

def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print ("%s processing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待队列清空
while not workQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")

运行结果如下

开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-3
Thread-3 processing One
Thread-2 processing Two
Thread-3 processing Three
Thread-1 processing Four
Thread-2 processing Five
退出线程:Thread-3
退出线程:Thread-1
退出线程:Thread-2
退出主线程

三、Celery

前面我们在 “消息队列” https://my.oschina.net/u/3314358/blog/1818966 中具体进行了 celery 操作,这里我们主要看下代码的含义

BROKER_URL = 'amqp://'                              # broker设置  
CELERY_RESULT_BACKEND = 'amqp://'                   # 存储任务结果  
CELERY_TASK_RESULT_EXPIRES = 18000                  # celery任务结果有效期  
  
CELERY_TASK_SERIALIZER = 'json'                     # 任务序列化结构  
CELERY_RESULT_SERIALIZER = 'json'                   # 结果序列化结构  
CELERY_ACCEPT_CONTENT=['json']                      # celery接收内容类型  
CELERY_TIMEZONE = 'Asia/Shanghai'                   # celery使用的时区  
CELERY_ENABLE_UTC = True                            # 启动时区设置  
CELERYD_LOG_FILE="/var/log/celery/celery.log"       # celery日志存储位置  
from kombu.common import Broadcast  
CELERY_QUEUES = (Broadcast('broadcast_logger'), )   # 任务队列的类型  
CELERY_ROUTES = {                                   # 任务队列  
'log_analysis.run': {'queue': 'api.log'},  
'logrotate': {'queue': 'broadcast_logger'},  
}  
CELERY_SEND_TASK_ERROR_EMAILS = True                # celery接收错误邮件  
ADMINS = (  
    ("*****", "*****@***.com"),                     # celery接收错误邮件地址  
)   
SERVER_EMAIL = ****@***.com                         # 从哪里发送的错误地址  
EMAIL_HOST = "*.*.*.*"                     
EMAIL_PORT = 25  
EMAIL_HOST_USER = SERVER_EMAIL   
CELERYBEAT_SCHEDULE = {                             # 定期执行任务  
# 接口中心每小时  
'api.hour':{'task': 'api.hour', 'schedule': crontab(minute=15), 'args': ()},  
# 接口中心每日  
'api.day':{'task': 'api.day', 'schedule': crontab(minute=30, hour=0), 'args': ()},  
}  
celery = Celery()  
celery.config_from_object('celeryconfig1')          # celery配置文档  

什么是broker?

broker 是一个消息传输的中间件,可以理解为一个邮箱。每当应用程序调用 celery 的异步任务的时候,会向 broker 传递消息,而后 celery 的 worker 将会取到消息,进行对于的程序执行。好吧,这个邮箱可以看成是一个消息队列。其中 Broker 的中文意思是 经纪人 ,其实就是一开始说的 消息队列 ,用来发送和接受消息。这个 Broker 有几个方案可供选择:RabbitMQ (消息队列),Redis(缓存数据库),数据库(不推荐),等等

什么是 backend?

通常程序发送的消息,发完就完了,可能都不知道对方时候接受了。为此,celery 实现了一个 backend,用于存储这些消息以及 celery 执行的一些消息和结果。Backend是在Celery的配置中的一个配置项 CELERY_RESULT_BACKEND,作用是保存结果和状态,如果你需要跟踪任务的状态,那么需要设置这一项,可以是Database backend,也可以Cache backend,具体可以参考这里: CELERY_RESULT_BACKEND 。

对于 brokers,官方推荐是 rabbitmq 和 redis,至于 backend,就是数据库。为了简单可以都使用 redis。
 

28013836_CTQd.png

可以看到,Celery 主要包含以下几个模块:

  • 任务模块 Task

    包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,而定时任务由 Celery Beat 进程周期性地将任务发往任务队列

  • 消息中间件 Broker

    Broker,即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等。

  • 任务执行单元 Worker

    Worker 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它

  • 任务结果存储 Backend

    Backend 用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, redis 和 MongoDB 等。

 

 

 

 

 

 

 

参考资料

1. http://www.runoob.com/python3/python3-multithreading.html

2. Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html

3. Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/

4. celery配置:http://docs.jinkan.org/docs/celery/configuration.html#configuration

5. http://www.cnblogs.com/landpack/p/5564768.html

6. http://blog.csdn.net/happyAnger6/article/details/51408266

7. http://www.cnblogs.com/forward-wang/p/5970806.html

8. 分布式队列神器 Celery:https://segmentfault.com/a/1190000008022050

9. celery最佳实践:https://my.oschina.net/siddontang/blog/284107

10. Celery 分布式任务队列快速入门:http://www.cnblogs.com/alex3714/p/6351797.html

11.异步任务神器 Celery 快速入门教程:https://blog.csdn.net/chenqiuge1984/article/details/80127446

12. 定时任务管理之python篇celery使用:http://student-lp.iteye.com/blog/2093397

13. 异步任务神器 Celery:http://python.jobbole.com/87086/

14. celery任务调度框架实践:https://blog.csdn.net/qq_28921653/article/details/79555212

15. 用户指南 https://blog.csdn.net/libing_thinking/article/details/78592801

16. Celery安装及使用:https://blog.csdn.net/u012325060/article/details/79292243

17. Celery学习笔记(一):https://blog.csdn.net/sdulsj/article/details/73741350

18. http://docs.celeryproject.org/en/latest/whatsnew-4.0.html#removed-features windows is not officially supported by celery 4

19. https://my.oschina.net/siddontang/blog/284107

20. https://denibertovic.com/posts/celery-best-practices/

转载于:https://my.oschina.net/u/3314358/blog/1819663

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值