JavaScriptのcanvasで複数の画像をロードするには

やりたいことは表題の通りです。
stackoverflowやらブログやらとなんかいろいろと情報が転がっていますが、

http://www.html5canvastutorials.com/tutorials/html5-canvas-image-loader/

↑こちらのリンク先のコード一択だと思います(`・ω・´)
読みやすくて使いやすい!

人様の記事のグーグラビリティを上げるだけの記事でした。(グーグラビリティという単語が入っているとむしろ下がりそう)

Gruntを使ってファイル変更後ブラウザで自動リロード

HTMLやJS、CSSを編集したらいちいちブラウザをリロードするのは面倒ですよね。いろんな人が書いているので何番煎じだって感じですが、動く例として。2014-11-05の時点でMacとDebianで確認しました。

npmなどは事前にインストールしておいてください。

# gruntをインストール
$ sudo npm install grunt-cli -g

# ディレクトリを作成して移動
$ mkdir grunt-livereload-example
$ cd grunt-livereload-example/

# 必要なパッケージをインストール
$ sudo npm install grunt-contrib-watch grunt-contrib-connect connect-livereload

# index.htmlを作成 (ソースは後ろに付加)
$ vi index.html

# Gruntfile.jsを作成 (ソースを後ろに付加)
$ vi Gruntfile.js

# gruntを実行
$ grunt

# ブラウザで、127.0.0.1:9000 にアクセス!

index.html
<script src="http://127.0.0.1:35729/livereload.js"></script>

<div>
ここにいろいろと書いてみて、保存すると、ブラウザが再読み込みしてくれます。
</div>

Gruntfile.js
var LIVE_RELOAD_PORT = 35729;
var LIVE_RELOAD_SNIPPET = require('connect-livereload')({port: LIVE_RELOAD_PORT});
var LIVE_RELOAD_MOUNT_FOLDER = function (connect, dir) {
	return connect.static(require('path').resolve(dir));
};

module.exports = function(grunt) {

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-connect');

  grunt.initConfig({
    watch: {
      files: 'index.html', // 監視対象のファイル
      options: {
        livereload: true //livereloadを有効
      }
    },
    connect: {
      options: {
        port: 9000,
        hostname: '0.0.0.0'
      },
      livereload: {
        options: {
          middleware: function (connect) {
            return [LIVE_RELOAD_SNIPPET, LIVE_RELOAD_MOUNT_FOLDER(connect, './')];
          }
        }
      }
    }
  });

  grunt.registerTask('default', ['connect', 'watch']);
};

蛇足
– HTMLにスニペットを入れてありますので、Chromeのアドオンなどは必要ありません
– livereloadのポートの変更は、Gruntfile.jsのLIVE_RELOAD_PORTの値を書き換えて、HTMLにスニペットを反映し、gruntを再起動すればOKです
– HTMLのスニペットのIPアドレスを書きければ、ローカルではなくリモートのブラウザでも動きます。

PHPとRedisでロック

追記@2014-11-05

自分の要件を考えてみると、わざわざこんなことしなくても素直にMySQLを使えばよいことに気が付きました><


DB鯖がたくさん

それらに一貫性のある書き込みをしたい

分散一意IDの発行みたいなのは出来ない(emailとかusernameみたいな一意なやつを書きたい)

Redisでロック・アンロックして、一意性のある書き込みの場合はたくさんのDB鯖に書き込めるプロセスを一個にしよう

という動機でRedisを使ったlock/unlockメソッドを書いてみました。詳しくはソース内↓に書いてます。けっこう苦労しました(ヽ’ω`)。
ロック周りは大変ですね。これが正解かは分かりません(というかCAP定理より全部を満たすことは出来ないですね)。おそらくロック用Redisサーバーを専用に用意する必要があるかと思います。というのも、これはロックを得るための待ち行列ができるからです。Redisで「 $ ./src/redis-cli -p 26379 monitor」としてみると、アクセスがたくさんあるのが分かるかと思います。

<?php
/**
 * PHPとRedisを用いてロックを実現するメソッドと使用例。
 * 
 * 分散された複数のデータベースサーバーについて、一貫性のある書き込みやupdateを行うために、Redisをロックサーバーとして用いる。
 * lockとunlockメソッドがある。
 * 使用する際は、lockメソッドでロックしてから、書き込みやアップデートして、後始末としてアンロックする。
 * 具体的な使い方は下記にある。
 * 
 * 留意点:
 * - DB_LOCK_TIMEOUT_FOR_DEADLOCK_SEC > (処理したい内容に掛かる時間) とせよ
 * → デッドロック回避のためにタイムアウトが指定されている。
 * → つまりロック後に、指定した時間以上に時間が掛かる処理だとロックが自動的に解除されて、
 * → 他のプロセスやスレッドがロックしてアクセスしてきて、一貫性が乱れる可能性がある。
 * 
 * - lockメソッドでロックを取得できない場合がある
 * → あまりにも多くのプロセスがロックを得ようとして、いつまでも待たされて、
 * → DB_LOCK_TIMEOUT_TOO_BUSY_SEC で設定した時間を超えるとFALSEが返ることがある。そのため、
 * → DB_LOCK_TIMEOUT_TOO_BUSY_SEC > (処理したい内容に掛かる時間) * 想定されるプロセス数
 * → となるようにせよ。
 * → 今回の場合は、1sec掛かる処理を行っており、DB_LOCK_TIMEOUT_TOO_BUSY_SECは10である。
 * → つまり、並列プロセス数10程度でロック取得失敗が起きる可能性がある。実際このプログラムを12並列で動かすと、
 * → 「*** Too busy. can not get a lock. ***」が起きる。
 * 
 * - DB_LOCK_WAIT_MSを小さい値にしすぎるとRedisへの負担が掛かる
 * → プロセスがロックがあるかどうかとDB_LOCK_WAIT_MSの周期でRedisに尋ねるので、Redisの負担になる。
 * → 大きすぎず小さすぎずの値にしておくべきだと思う。0.1secつまり100msあたりが妥当かと。
 * → 定量的に考えてみる、一般的なマシンでRedisは10万req/sec程度さばくことが出来る。
 * → 1プロセスあたりの1秒間のrequestは1000/DB_LOCK_WAIT_MS。たとえばDB_LOCK_WAIT_MS=100なら10req/sec
 * → 10並列のプロセスで100req/sec、100並列で1000req/secとなる。
 * → 一般論として同時コネクション数は100程度にしておくことが無難であり、Redisへのsetnxのアクセスは限界の10%つまり1万req/sec程度に
 * → しておけば大きな負荷にはならないだろう。ということは、1プロセスあたり一秒間に100回問い合わせして良いということなので、
 * → DB_LOCK_WAIT_MSの最小値は10と考えられる。これ以下にすると危険であろう。
 * → 特にレイテンシが求められるわけでもないのであれば、結局最初に書いた通り100に設定するのが妥当。
 * 
 * 疑問点:
 * ロックのためのkeyとvalueを書き込んでいるが、valueが必要かどうか良く分からない。
 * さらにunlockではvalueの値を確認して削除しているが、必要なのかどうか。
 * valueは必要ない気がする。消すか悩む。
 * → 消してみたら何かおかしくなった。きちんとしていない。ので、valueは残しておく。
 * 
 * 蛇足:
 * - $ ./src/redis-cli -p 26379 monitor
 * とすることで、Redisへの命令とその時間を眺めることが出来る。
 * 
 * 参考資料:
 * - http://ameblo.jp/principia-ca/entry-11770810115.html
 * - https://engineering.gosquared.com/distributed-locks-using-redis
 */

// ==================== 定数とメソッド ====================
define('DB_LOCK_KEY_PREFIX', 'lock:');
define('DB_LOCK_VALUE_PREFIX', 'value:');
define('DB_LOCK_TIMEOUT_FOR_DEADLOCK_SEC', 3); // means 3 sec。処理したい内容に掛かる処理時間よりも2倍以上大きな値にせよ!
define('DB_LOCK_TIMEOUT_TOO_BUSY_SEC', 10); // means 10 sec。想定されるプロセス数*(処理したい内容に掛かる処理時間)よりも大きな値にせよ!
define('DB_LOCK_WAIT_MS', 100); // means 100 ms。10ms程度が限界だと思われる。
function lock(Redis $redis, $lock_key, $lock_value) {
	$now = time();
	while (TRUE) {
		$res = $redis->setnx(DB_LOCK_KEY_PREFIX . $lock_key, DB_LOCK_VALUE_PREFIX . $lock_value);
		if ($res === TRUE) {
			// デッドロック回避のためタイムアウトを指定
			$redis->setTimeout(DB_LOCK_KEY_PREFIX . $lock_key, DB_LOCK_TIMEOUT_FOR_DEADLOCK_SEC);
			return TRUE;
		}

		// 競合が多すぎて指定された時間(= DB_LOCK_TIMEOUT_TOO_BUSY_SEC)内に取れない場合、FALSE
		if (time() > $now + DB_LOCK_TIMEOUT_TOO_BUSY_SEC) {
			return FALSE;
		}

		// 待機
		usleep(DB_LOCK_WAIT_MS * 1000);
	}
}
function unlock(Redis $redis, $lock_key, $lock_value) {
	$res = $redis->get(DB_LOCK_KEY_PREFIX . $lock_key);
	if ($res === DB_LOCK_VALUE_PREFIX . $lock_value) {
		$redis->delete(DB_LOCK_KEY_PREFIX . $lock_key);
		return TRUE;
	} else {
		return FALSE;
	}
}

// ==================== 以下、活用例 ====================

define('REDIS_HOST', '127.0.0.1');
define('REDIS_PORT', 26379);
define('REDIS_TIMEOUT_SEC', 2.0); // means 2.0 sec

$redis = new Redis();
$redis->connect(REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT_SEC);

echo "========================================\n";
echo "Going to get a lock...\n";
if (lock($redis, 'key1', 'value1') === TRUE){
	echo "Lock ok.\n";
	echo "Going to do a task...\n";
	sleep(1); // task = 1 secs sleep!
	echo "Task done. Going to unlock.\n";
	if (unlock($redis, 'key1', 'value1') === TRUE){
		echo "Unlock ok.\n";
		echo "Yay! Everything done well!\n";
		echo "========================================\n";
	} else {
		echo "*** Unlock failed. ***\n";
		exit();
	}
} else {
	echo("*** Too busy. can not get a lock. ***\n");
	exit();
}