最近在写python项目的时候遇到一个问题

有这样一个python脚本:

1 脚本功能 

 A 监控网卡的实时流量,如果该流量超过设定阀值就去增加带宽(购买带宽包)

 B 脚本放在 crontab 中,并且是每分钟执行一次

2  遇到问题

 在执行脚本的过程中,如果一分钟内该程序没有执行完,就可能会有两个相同的代码同时执行,导致的问题是 会同时购买两次带宽包,这样会造成资源的浪费。

3  解决思路

A  可以调整crontab,增加代码执行的时间(治标不治本)

B  为该程序加锁,同一时间只允许运行一个监控程序。

4  解决步骤

因此,怎么为程序加锁,还要尽量减少对源代码的更改,是问题的关键,我这里使用到了python的装饰器。

思路是这样的:

A 打开一个 xx.pid 文件,并为这个文件加上锁

B 获取当前执行程序的PID,并写入到 xx.pid文件中

C 执行程序,执行结束后关闭并且删除该xx.pid文件

5  上代码

#!/usr/bin/python
# -*- coding: utf-8 -*-

import fcntl
from functools import wraps
import os


def singleton(pid_filename):
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            pid = str(os.getpid())
            pidfile = open(pid_filename, 'a+')
            try:
                #创建一个排他锁,并且所被锁住其他进程不会阻塞
                fcntl.flock(pidfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                return
            pidfile.seek(0)
            pidfile.truncate() # 清空文件
            pidfile.write(pid)
            pidfile.flush()
            pidfile.seek(0)

            ret = f(*args, **kwargs)

            try:
                pidfile.close()
            except IOError, err:
            # 可能的错误 IOError: [Errno 9] Bad file descriptor
                if err.errno != 9:
                    return
            os.remove(pid_filename)
            return ret
        return decorated
    return decorator

注意:

 fcntl 仅在 linux系统中使用,在windows中无效


6  简单用例

@singleton('/tmp/add_bandwidth.pid')
def add_bandwidth():
    pass


7  代码解析

这里使用到了python 的fcntl模块 -- python中给文件加锁

简单示例:

>>> import fcntl
>>> f = open('test.txt')
>>> fcntl.flock(f.fileno(),fcntl.LOCK_EX) #对文件加锁
>>> fcntl.flock(f.fileno(),fcntl.LOCK_UN) #对文件解锁

----------------------

fcntl模块 中的flock(fd, operation):

参数 fd 表示文件描述符;

参数 operation 指定要进行的锁操作,该参数的取值有如下几种:

 operation : 包括:
    fcntl.LOCK_UN  解锁
    fcntl.LOCK_EX  排他锁
    fcntl.LOCK_SH  共享锁
    fcntl.LOCK_NB  非阻塞锁
 LOCK_SH 共享锁:所有进程没有写访问权限,即使是加锁进程也没有。所有进程有读访问权限。
 LOCK_EX 排他锁:除加锁进程外其他进程没有对已加锁文件读写访问权限。
 LOCK_NB 非阻塞锁:如果指定此参数,函数不能获得文件锁就立即返回,否则,函数会等待获得文件锁。

 LOCK_NB可以同LOCK_SH或LOCK_NB进行按位或(|)运算操作。 fcnt.flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB)


注意:

1. 对于文件的 close() 操作会使文件锁失效;
2. 同理,进程结束后文件锁失效;

3. flock() 的 LOCK_EX是“劝告锁”,系统内核不会强制检查锁的状态,需要在代码中进行文件操作的地方显式检查才能生效。

详情请查看官方文档:

https://docs.python.org/2/library/fcntl.html#fcntl.flock