├── .gitignore ├── .travis.yml ├── README.md ├── demo └── app.js ├── index.js ├── lib ├── client.js ├── consts.js └── server.js ├── package.json └── test └── lock.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 介绍 2 | 3 | lockman 是一个用于多进程的并发控制锁, 类似一些语言中(比如 C#)的 lock 关键字可以用来确保代码块完成运行,而不会被其他进程「影响」。 4 | 它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个进程进入执行, 5 | 而其他进程必须等待。 6 | 7 | 不同之处,C# 的 lock 关键字作用在「线程间」,lockman 作用在 Node 的进程间。 8 | 9 | [![npm version](https://badge.fury.io/js/lockman.svg)](http://badge.fury.io/js/lockman) [![Build Status](https://travis-ci.org/Houfeng/lockman.svg?branch=master)](https://travis-ci.org/Houfeng/lockman) 10 | 11 | ### 安装 12 | 13 | ```sh 14 | $ npm install lockman --save 15 | ``` 16 | 17 | ### 示例 18 | 19 | ```js 20 | const Locker = require('lockman'); 21 | 22 | let locker = new Locker('demo'); 23 | 24 | locker.acquire(function(){ 25 | //此处代码在同一时刻只允许一个进程进入执行 26 | locker.release(); 27 | }); 28 | ``` -------------------------------------------------------------------------------- /demo/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Locker = require('../'); 4 | 5 | var locker = new Locker('a1'); 6 | 7 | console.time('test'); 8 | var count = 0, max = 5000; 9 | for (let i = 0; i < max; i++) { 10 | locker.lock(function () { 11 | //console.log(i); 12 | if (++count >= max) 13 | console.timeEnd('test'); 14 | locker.unlock(); 15 | }); 16 | } 17 | 18 | var locker2 = new Locker('a2'); 19 | locker2.lock(function () { 20 | console.log('a2', 0); 21 | locker2.unlock(); 22 | }); 23 | 24 | locker2.lock(function () { 25 | console.log('a2', 1); 26 | locker2.unlock(); 27 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Client = require('./lib/client'); 2 | const Server = require('./lib/server'); 3 | 4 | const Locker = Client.Locker; 5 | 6 | Locker.Client = Client; 7 | Locker.Server = Server; 8 | Locker.ClientLocker = Client.Locker; 9 | Locker.ServerLocker = Server.Locker; 10 | Locker.Locker = Client.Locker; 11 | 12 | module.exports = Locker; -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | const Class = require('cify').Class; 2 | const utils = require('ntils'); 3 | const net = require('net'); 4 | const split = require('split'); 5 | const consts = require('./consts'); 6 | const Server = require('./server'); 7 | const oneport = require('oneport'); 8 | 9 | /** 10 | * 锁客户端 11 | **/ 12 | const Client = new Class({ 13 | 14 | /** 15 | * 构造一个锁链接对象 16 | **/ 17 | constructor: function (options) { 18 | options = options || {}; 19 | this.options = options; 20 | this._connecteStatus = 0; 21 | this._connecteClabacks = []; 22 | this._serverCallbacks = {}; 23 | this._connTryCount = 0; 24 | }, 25 | 26 | /** 27 | * 进程链接 28 | **/ 29 | _connect: function (callback) { 30 | if (this._connecteStatus == 2) return callback(); 31 | if (callback) this._connecteClabacks.push(callback); 32 | if (this._connecteStatus == 1) return; 33 | this._connecteStatus = 1; 34 | //进行连接尝试 35 | this._connectServer(); 36 | }, 37 | 38 | /** 39 | * 启动并连接 40 | **/ 41 | _startServerAndConnect: function () { 42 | this._connTryCount++; 43 | if (this._connTryCount > consts.CONN_TRY_NUM) { 44 | return utils.each(this._serverCallbacks, function (id, callback) { 45 | if (callback) callback(new Error('Connection failed')); 46 | }); 47 | }; 48 | if (this.options.autoCreateServer !== false) { 49 | this._server = new Server(this.options); 50 | this._server.start(this._connectServer()); 51 | } else { 52 | this._connectServer(); 53 | } 54 | }, 55 | 56 | /** 57 | * 连接到锁控制服务 58 | **/ 59 | _connectServer: function () { 60 | this._client = new net.Socket(); 61 | //出错时重连 62 | this._client.on('error', this._startServerAndConnect.bind(this)); 63 | this._client.on('close', this._startServerAndConnect.bind(this)); 64 | this._client.on('end', this._startServerAndConnect.bind(this)); 65 | //-- 66 | oneport.last(consts.PORT_KEY, function (portErr, port) { 67 | if (portErr || !port) { 68 | return setTimeout(this._startServerAndConnect.bind(this), 0); 69 | } 70 | this._client.connect(port, consts.HOST, function (err) { 71 | if (this._connecteStatus == 2) return; 72 | this._connecteStatus = 2; 73 | this._connTryCount = 0; 74 | this._client.pipe(split()).on('data', function (id) { 75 | if (!id) return; 76 | id = id.toString(); 77 | var callback = this._serverCallbacks[id]; 78 | if (callback) callback(err); 79 | delete this._serverCallbacks[id]; 80 | }.bind(this)); 81 | while (this._connecteClabacks.length > 0) { 82 | this._connecteClabacks.shift()(err, this._client); 83 | } 84 | }.bind(this)); 85 | }.bind(this)); 86 | }, 87 | 88 | /** 89 | * 调用锁管理服务 90 | **/ 91 | _call: function (info, callback) { 92 | callback = utils.isFunction(callback) ? callback : consts.NOOP; 93 | this._connect(function (err) { 94 | if (err) return callback(err); 95 | info.push(utils.newGuid()); 96 | this._serverCallbacks[info[2]] = callback; 97 | this._client.write(info.join(',') + '\n'); 98 | }.bind(this)); 99 | } 100 | 101 | }); 102 | 103 | /** 104 | * 默认连接实例 105 | **/ 106 | const DEFAULT_CLIENT = new Client(); 107 | 108 | /** 109 | * 客户端 Locker 类 110 | **/ 111 | Client.Locker = new Class({ 112 | 113 | /** 114 | * 构造一个指定名称的锁 115 | * @param {String} 锁名称 116 | * @param {Client} 锁连接实例 117 | **/ 118 | constructor: function (name, client) { 119 | if (!name || 120 | name.length < 1 || 121 | name.length > 32 || 122 | !(/^[a-z0-9\-\_\$]+$/igm.test(name))) { 123 | throw new Error('Involid locker name:', name); 124 | } 125 | this.name = name; 126 | this._client = client || this.$class.client || DEFAULT_CLIENT; 127 | }, 128 | 129 | /** 130 | * 审申锁控制权 131 | * @param {Function} action 拿到控制权后执行的函数 132 | **/ 133 | acquire: function (action) { 134 | if (!utils.isFunction(action)) return; 135 | this._client._call(['acquire', this.name], action); 136 | }, 137 | 138 | /** 139 | * 释放锁控制权 140 | * @param {Function} callback 释放成功后的回调 141 | **/ 142 | release: function (callback) { 143 | callback = utils.isFunction(callback) ? callback : consts.NOOP; 144 | this._client._call(['release', this.name], callback); 145 | }, 146 | 147 | /** 148 | * 审申锁控制权(acquire 的别名) 149 | * @param {Function} action 拿到控制权后执行的函数 150 | **/ 151 | lock: function (action) { 152 | this.acquire(action); 153 | }, 154 | 155 | /** 156 | * 释放锁控制权(release 的别名) 157 | * @param {Function} callback 释放成功后的回调 158 | **/ 159 | unlock: function (callback) { 160 | this.release(callback); 161 | } 162 | 163 | }); 164 | 165 | module.exports = Client; -------------------------------------------------------------------------------- /lib/consts.js: -------------------------------------------------------------------------------- 1 | exports.HOST = '127.0.0.1'; 2 | exports.PORT_KEY = '9e45ee53-4534-35e9-f328-7a04c89d4307'; 3 | exports.CONN_TRY_NUM = 5; 4 | exports.NOOP = function () { }; -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | const Class = require('cify').Class; 2 | const utils = require('ntils'); 3 | const net = require('net'); 4 | const split = require('split'); 5 | const consts = require('./consts'); 6 | const oneport = require('oneport'); 7 | 8 | /** 9 | * 锁管理 Server 10 | **/ 11 | const Server = new Class({ 12 | 13 | /** 14 | * 构造 15 | **/ 16 | constructor: function (options) { 17 | options = options || {}; 18 | this.options = options; 19 | this._lockers = {}; 20 | }, 21 | 22 | /** 23 | * 审申指定名称的锁 24 | * @param {String} name 锁名称 25 | * @param {Function} action 申请成功后的动作 26 | **/ 27 | acquire: function (name, action) { 28 | action = utils.isFunction(action) ? action : consts.NOOP; 29 | this._lockers[name] = this._lockers[name] || new Server.Locker(); 30 | var locker = this._lockers[name]; 31 | locker.actionStack.push(action); 32 | if (locker.locked) return; 33 | locker.locked = true; 34 | this._execute(name); 35 | }, 36 | 37 | /** 38 | * 执行申请锁的动作(这里会触发通知 client 执行) 39 | **/ 40 | _execute: function (name) { 41 | var locker = this._lockers[name]; 42 | if (!locker) return; 43 | var action = locker.actionStack.shift(); 44 | if (utils.isFunction(action)) action(); 45 | }, 46 | 47 | /** 48 | * 释放指定名称的锁 49 | * @param {String} name 锁名称 50 | * @param {Function} 释放成功后的回调 51 | **/ 52 | release: function (name, callback) { 53 | callback = utils.isFunction(callback) ? callback : consts.NOOP; 54 | var locker = this._lockers[name]; 55 | if (locker && locker.actionStack.length > 0) { 56 | this._execute(name); 57 | } else { 58 | delete this._lockers[name]; 59 | } 60 | return callback(); 61 | }, 62 | 63 | /** 64 | * 启动锁管理 server 65 | * @param {Function} callback 启动成功时的回调 66 | **/ 67 | start: function (callback) { 68 | callback = utils.isFunction(callback) ? callback : consts.NOOP; 69 | this._server = net.createServer(function (socket) { 70 | socket.pipe(split()).on('data', function (data) { 71 | if (!data) return; 72 | var info = data.toString().split(','); 73 | this[info[0]]([info[1]], function () { 74 | socket.write(info[2] + '\n'); 75 | }); 76 | }.bind(this)); 77 | }.bind(this)); 78 | this._server.on('error', callback); 79 | oneport.acquire(consts.PORT_KEY, function (err, port) { 80 | if (err) return callback(err); 81 | this._server.listen(port, consts.HOST, callback); 82 | }.bind(this)); 83 | } 84 | 85 | }); 86 | 87 | /** 88 | * 服务端 Locker 类 89 | **/ 90 | Server.Locker = new Class({ 91 | 92 | constructor: function () { 93 | this.actionStack = []; 94 | this.locked = false; 95 | } 96 | 97 | }); 98 | 99 | module.exports = Server; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lockman", 3 | "version": "1.0.2", 4 | "description": "一个用于多进程的并发控制锁", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "keywords": [ 10 | "locker", 11 | "lock", 12 | "lockman" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/Houfeng/lockman.git" 17 | }, 18 | "author": "Houfeng", 19 | "license": "MIT", 20 | "dependencies": { 21 | "cify": "^2.1.1", 22 | "ntils": "^2.0.3", 23 | "oneport": "^1.0.2", 24 | "split": "^1.0.0" 25 | }, 26 | "devDependencies": { 27 | "mocha": "^3.0.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/lock.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const Locker = require('../'); 3 | 4 | describe('lockman', function () { 5 | 6 | it('lock', function (done) { 7 | var locker1 = new Locker('locker1'); 8 | var mark = 0; 9 | locker1.acquire(function () { 10 | setTimeout(function () { 11 | mark++; 12 | locker1.release(); 13 | }, 500); 14 | }); 15 | locker1.acquire(function () { 16 | mark++; 17 | assert.equal(mark, 2); 18 | done(); 19 | locker1.release(); 20 | }); 21 | }); 22 | 23 | }); 24 | --------------------------------------------------------------------------------