Table Of Contents

Previous topic

プロトコル仕様

This Page

仮想メモリ技術仕様

このドキュメントでは、Redisの仮想メモリサブシステムの内部の詳細を説明します。このドキュメントはユーザのためのものではなく、仮想メモリ実装を理解したい人や、手を加えたいプログラマーのためのものです。

キーvs値: スワップアウトとは何か?

VMサブシステムの目標は、Redisのオブジェクトをメモリからディスクに移すことによって、メモリを空けることにあります。Redisは値のオブジェクトのみを転送します。この概念を理解しやすくするために、 DEBUG コマンドを使用して、Redisの内部では、どのようにキーと値がひもづけられているのかを確認してみましょう。

redis> set foo bar
OK
redis> debug object foo
Key at:0x100101d00 refcount:1, value at:0x100101ce0 refcount:1 encoding:raw serializedlength:4

上記の出力例から分かるように、Redisのトップレベルのハッシュテーブルは、Redisオブジェクト(キー)から、他のRedisオブジェクト(値)への対応表となっています。仮想メモリは、値のオブジェクトだけをディスクにスワップし、キーのオブジェクトはメモリ内に保持します。これは、VMが無効状態のRedisと、VMが有効になっているRedisで、良く使われるデータセットがメモリに収まっている場合は同じぐらいのパフォーマンスを発揮するようにするという設計の主な目標を達成する仕様になっています。

スワップの内部動作

オブジェクトがスワップされる時は、次のようなことが発生します。

  • キーには、引き続きキーを表すRedisオブジェクトが保持される
  • 値には NULL がセットされる

これだけを見ると、キーに関連した値の情報はどこに格納されているのか疑問に思うでしょう。それは、キーオブジェクトの中に格納されます。

Redisオブジェクト構造体のrobjは次のような実装になっています。

/* Redisオブジェクトの実体 */
typedef struct redisObject {
    void *ptr;
    unsigned char type;
    unsigned char encoding;
    unsigned char storage;  /* もしこのオブジェクトがキーなら、値がどこにあるのか?
                             * REDIS_VM_MEMORY, REDIS_VM_SWAPPED, ... */
    unsigned char vtype;    /* もしこのオブジェクトがキーでスワップされている場合、,
                             * スワップされたたオブジェクトの種類を表す */
    int refcount;
    /* VM属性。これはVM機能が有効な時だけ割り当てられられる。
     * そうでない時は、オブジェクトの割り当て関数は、
     * ``sizeof(redisObjct) - sizeof(redisObjectVM)sizeof(redisObjct)`` の分のメモリ
     * しか確保しないため、VMが無効の場合は常に他のオブジェクトに上書きされる。 */
    struct redisObjectVM vm;
} robj;

この中には、VMに関する属性がいくつかあります。この中で最も重要なのは、 storage です。

REDIS_VM_MEMORY

関連する値はメモリの中にある。

REDIS_VM_SWAPPED

関連する値はスワップされていて、ハッシュテーブルの値のエントリーには NULL が設定されている。

REDIS_VM_LOADING

値はディスクにスワップされてエントリーは NULL であるが、現在メモリにスワップされたオブジェクトをロードするジョブが実行中。このフィールドはスレッド対応仮想メモリが有効な場合にのみ使用される。

REDIS_VM_SWAPPING

値はメモリにあり、このポインタは実際にRedisオブジェクトを指しているが、この値をスワップファイルに転送するI/Oジョブが稼働中。

オブジェクトがディスクにスワップされている(REDIS_VM_SWAPPED あるいは REDIS_VM_LOADING)場合、どこに格納されているのかを知るにはどうすればいいのでしょうか?また、その値のタイプはなんでしょうか?これはとてもシンプルです。スワップされたオリジナルのRedisオブジェクトのタイプは、 vtype 属性に格納されています。また、格納されている場所も、 redisObjectVM 構造体型の vm 属性の中に保持されています。この構造体には追加の属性が定義されています。

/* 仮想メモリオブジェクトの構造体 */
struct redisObjectVM {
    off_t page;         /* オブジェクトが格納されているディスク上のページ */
    off_t usedpages;    /* ディスクで使用しているページ数*/
    time_t atime;       /* 最後にアクセスした時間 */
} vm;

この構造体には、スワップファイルの中のどこにオブジェクトが格納されているかというページの場所の情報と、使用しているページ数、また、最後にアクセスした時間の情報が書かれています。この時間情報は、アクセスの少ないオブジェクトをスワップするときに、候補を選択するアルゴリズムから使用されます。

このコードを見ての通り、以前のRedisオブジェクトの構造体の他のすべての属性は使われても(メモリのアラインで空白領域はできるかもしれませんが)、この新しい vm 属性によって、追加のメモリが使用されます。このメモリのコストはVMが無効な時は払う必要はありません。次のコードはRedisオブジェクトを作るときのコードになります。

... some code ...
     if (server.vm_enabled) {
         pthread_mutex_unlock(&server.obj_freelist_mutex);
         o = zmalloc(sizeof(*o));
     } else {
         o = zmalloc(sizeof(*o)-sizeof(struct redisObjectVM));
     }
... some code ...

仮想メモリが無効な場合には、 sizeof(*o)-sizeof(struct redisObjectVM) 分しかメモリを確保していません。 vm 属性は構造体の最後にあるため、他のオブジェクトとメモリ空間がオーバーラップしても問題はなく、仮想メモリを使用しない場合にはメモリのオーバーヘッドは発生しません。

スワップファイル

VMサブシステムを理解するための次のステップとして、オブジェクトがスワップファイルに格納される仕組みを見て行きます。スワップファイルで使っているフォーマットは特別なものではなく、 SAVE コマンドを使った時にRedisが通常作成しているダンプファイルに、 .rdb ファイル内にオブジェクトが格納される時に使われるのと同じフォーマットです。

スワップファイルは指定されたサイズ(バイト数)と、指定されたページ数を持つように作られます。これらのパラメータは redis.conf の中で変えることができます。実際に格納するデータのサイズによって、適切なサイズは変わってくるでしょう。下の設定がデフォルト値です。

vm-page-size 32
vm-pages 134217728

Redisは「ビットマップ」をメモリ中に保持しています。これは、連続したビット列で、ゼロかイチが格納されます。それぞれのビットは、スワップファイル中のページを表します。もし、1がセットされていれば、そのページは既に仕様されていて、Redisのオブジェクトが格納されています。ゼロがセットされている場合は、そのページは利用可能であることを表しています。

このビットマップ(ページテーブルと呼ばれます)をメモリ中に持つことで、パフォーマンスの面で優れていると同時に、メモリ使用量も押さえられた実装になっています。ページごとに1ビットしか必要でないため、デフォルトの32ビット、1.3億ページ(4GBのスワップ)が確保された場合でも、ページテーブルは16MBしかありません。

メモリからスワップにオブジェクトを転送する

オブジェクトをメモリからディスクにスワップする場合は、次のステップで行われます。なお、この説明はブロック処理がシンプルな、スレッドを使わない仮想メモリを想定しています。

  • このオブジェクトをディスクにスワップするには、何ページのブロックが必要かを探します。 rdbSavedObjectPages 関数がこの計算を行い、オブジェクトが使用することになるページ数を返します。この関数は .rdb ファイルに保存するコードの複製、ディスクに保存した後のサイズを計算するものです。この中では、 /dev/null にオブジェクトを書き込んで、最後の ftello を呼び出すことで必要なバイト数を計算するというトリックを使っています。この中で行っているのは基本的に仮想のとても高速なファイルである /dev/null への書き込みです。
  • スワップファイル内に、何ページのスペースが必要かが分かったら、次はスワップファイル内の連続したフリーのページを探しに行きます。これを行うのが vmFindContiguousPages です。この関数は、スワップファイルがいっぱいになっているか、断片化して十分な容量がない場合に失敗します。この場合はスワップ処理が異常終了し、オブジェクトはメモリ内に存在し続けます。

最後に、決まった位置にオブジェクトをディスクに書きこみます。これを行うのは vmWriteObjectOnSwap です。

オブジェクトがスワップファイルに正しく書き込まれると、メモリは解放されます。関連するキーの storage 属性には REDIS_VM_SWAPPED が設定され、 usedpages にはページテーブル内のページが書き込まれます。

メモリにオブジェクトをロードする

スワップファイルからメモリにオブジェクトをロードする仕組みはシンプルです。スワップファイルのどこに、何ページ分保存されているかということは既にわかっています。また、オブジェクトの種類(ディスク上にはこの情報は保存されていないため、ロードする関数はこれを知っている必要がある)を知る必要がありますが、これも上記の構造体の vtype 属性に保存されています。

key オブジェクトを渡して vmLoadObject 関数を呼べば、ロードは完了します。この関数の中では、保存場所の情報の修正をしたり(REDIS_VM_MEMORY になる)、ページテーブルを解放したりします。

この関数の返り値はロードされたRedisオブジェクトそのものであり、スワップされたときに NULL に設定されたメインのハッシュテーブルに設定しなければなりません。

どのようにブロッキング仮想メモリは動作するのか?

これまでのところで、仮想メモリの動作のブロッキングに必要な材料がそろいました。まず最初に設定の重要なところを紹介します。仮想メモリのブロッキングを有効にするには、Redisサーバの vm_max_threads をゼロに設定する必要があります。スレッド対応の仮想メモリの時に、どのようにこの最大スレッド数の設定が使用されるかは、この後で説明します。この値をゼロにすることで、完全なブロッキングを行う仮想メモリとして動作します。

もうひとつの重要な仮想メモリの属性として、 vm_max_memory があります。このパラメータはスワップのトリガーを設定するために重要となります。Redisは、このメモリの設定値を超えたメモリを使用した場合にのみ、スワップを行おうとします。この値に到達しない場合は、スワップの必要はないものとして動作します。

ブロッキング仮想メモリのスワップ

メモリからディスクへのスワップは、 cron 関数の中で行われます。現在のバージョンではこの関数は毎秒呼び出されますし、git上の最新バージョンでは100ミリ秒(1秒に10回)呼ばれます。この関数の中で、メモリ使用量の限界(vm-max-memory の設定値)を超えたことが検知されると、ループの中で vmSwapOneObect を呼び出して、ディスクへの移動を行います。この関数は1つ引き数を取りますが、もし0を渡すと、ブロッキングした状態でスワップを行います。1が設定されると、I/Oスレッドがしようされます。このブロッキング仮想メモリの説明の中では、0が渡されたものとして話を進めます。

vmSwapOneObject は次のように動作します。

  • キーのテーブルを探索して、スワップするデータの候補を探す。スワップの候補の選び方は後で紹介します。
  • ブロックされた中で、ディスクに値を転送する。
  • キーの storage 属性に REDIS_VM_SWAPPED をセットし、 vm 属性の中の変数(ページ番号や、ページ数)に正しい値を設定する。
  • 最後に値オブジェクトを会報誌、ハッシュテーブルの値のエントリーに NULL を設定する。

この関数は次の状態になるまで繰り返し呼ばれます。

  • スワップファイルがいっぱいになった
  • すべてのオブジェクトがすでにディスクに転送されている
  • メモリ使用量が vm-max-memory の設定よりも少なくなった

メモリがあふれたときに、どの値をスワップするのか?

スワップする候補を選ぶロジックの理解は難しくありません。いくつかのオブジェクトをランダムでサンプリングし、それぞれの swappability 値を次のように計算します。

swappability = age*log(size_in_memory)

age は最後にアクセスされてからの秒数です。 size_in_memory はオブジェクトが利用している、メモリのバイト数です。アクセスされる頻度が少なく、大きいオブジェクトほど、スワップされやすくなります。ただし、logをとっているため、大きさの重みは小さくなっています。サイズの大きなオブジェクトを読み書きすることは、I/OやCPUに負荷をかけるので、あまり転送したくはないためです。

ブロッキング仮想メモリのロード

スワップされたオブジェクトを持つキーに対する命令が発行された場合、どのようなことが行われるのでしょうか?例えば、次のような操作が行われる可能性があります。

GET foo

foo キーの値オブジェクトがスワップされている場合、操作を実行する前に、メモリにロードし直す必要があります。Redisのキー探索の処理は、 lookupKeyRead と、 lookupKeyWrite の2つの関数に集約されています。これらの関数は、キー空間にアクセスするすべてのRedisコマンドの実装の中から使用されています。そのため、スワップファイルからメモリにロードする処理も、この場所で行われます。

次のようなことが行われます。

  • ユーザが、スワップされたキーを引き数に取るコマンドを呼び出す
  • コマンドの実装コードから、キー探索関数が呼ばれる
  • 探索関数は、トップレベルのハッシュテーブルからキーを探す。もしキーに関連付けられた値がスワップされている場合(key オブジェクトの storage 属性を見て確認)、ブロックを行い、ユーザに処理が返る前に、メモリにロードしなおす。

この場合は極めて素直な処理になっていますが、スレッドが絡んでくると、もっと動きが楽しくなってきます。ブロッキング仮想メモリの観点で見ると、 BGSAVE や、 BGREWRITEAOF コマンドなどにより、データセットが別のプロセスから保存されることだけが注意すべきことになります。

仮想メモリがアクティブな時の、バックグラウンドセーブ

Redisはデフォルトでは、子プロセスを使って、ディスク上に .rdb ファイルを作って、保存をします。Redis()は fork システムコールを呼び出して子プロセスを作ります。このとき、プログラムのメモリ空間が複製されます。(実際には、copy on writeと呼ばれる技術により、親プロセスと子プロセスの間ではメモリが共有されるため、forkが使用するメモリは2倍にはなりません。)

子プロセスでは、forkされたタイミングでのデータセットのコピーを持っています。クライアントから何かコマンドを受け取って、親プロセスが処理を行ったとしても、子のデータは変更されません。

子プロセスはすべてのデータセットを、 dump.rdb ファイルにダンプして終了します。もし仮想メモリがアクティブになっている場合、何が起きるのでしょうか?値がスワップされているため、すべてのデータがメモリに格納されているわけではありません。そのため、スワップされた値を読み込むためには、スワップファイルにアクセスしなければなりません。

  • 親プロセスも、スワップされた値を処理する時は、スワップファイルにアクセスする必要があります。
  • 子プロセスも、全データセットをディスクに保存する場合に、スワップファイルにアクセスする必要があります。

同じスワップファイルに同時にアクセスする問題を避けるために、Redisではバックグラウンドセーブを行っているあいだは、親プロセスが値をスワップアウトすることを許可しない、というシンプルな方法を採用しています。この場合、両方のプロセスは、読み込み専用でのアクセスすることになります。このアプローチでは、子プロセスが保存をしているあいだは、親プロセスが一時的に最大メモリ使用量のパラメータ以上のメモリを使用してしまう可能性がある、という問題があります。ですが、バックグラウンドのセーブは短時間で終了されるため、あまり問題になりませんし、スワップが必要であれば、すぐにスワップが行われるでしょう。

追記専用ファイルモードを有効にしていると、 BGREWRITEAOF コマンドを実行して、ログの再書き込みをしている場合にのみ、この問題が起きる可能性があります。

ブロッキング仮想メモリの問題

ブロッキング仮想メモリの問題は・・・ブロッキングすることです :) これは、Redisをバッチプロセスに対して使用している場合には問題になりませんが、遅延時間が少ないことが要求される場面で、リアルタイムにどんどん処理を行うようなRedisサーバを運用している場合は、問題となるでしょう。ブロッキング仮想メモリは、クライアントがスワップされた値にアクセスする命令が送ったり、Redisが値をスワップする必要がある場合、他のクライアントに対するサービスが止まるため、非常に処理が遅くなります。

スワップはバックグラウンドで行われるべきです。また、スワップされた値にアクセスされている時に、他のクライアントからメモリ上にある値へのアクセスが行われても、仮想メモリがオフになっているときと同じぐらい高速で行われるべきです。スワップされたキーに対するアクセスがあったときの遅延だけが許されます。

このような制約をすべて回避したいですよね? ノンブロッキング仮想メモリ実装の出番です。

スレッド対応仮想メモリ

ブロッキング仮想メモリを、ノンブロッキング仮想メモリにするには、主に次の3通りの方法があります。

  1. 1つ目の方法はRedis自身をスレッド化したサーバとして実装する方法です。もし、すべてのリクエストを異なるスレッドで自動で処理するようになれば、クライアントは他のクライアントによるブロックを待つ必要がなくなります。これは、分かりやすいのですが、私の意見ではあまり良いアイディアではありません。Redisは高速で、アトミックな操作を行えるようにしています。この1万行ほどのコードはシングルスレッドで処理されるため、この中ではロックが行わないで処理されます。そのため、これは私の選択肢には入りません。
  1. スワップファイルに対して、ノンブロッキングI/Oを使用します。Redisはすでにイベントループベースの実装になっているため、なぜノンブロッキングな方法でディスクI/Oを扱わないのでしょうか?私は、次に挙げる2つの理由から、この選択肢を捨てました。一つ目は、ノンブロッキングなファイル操作は、ソケットとは異なり、不一致による悪夢が起きる、ということです。単に select を呼べば良いというだけではなく、OS依存のAPIを使う必要があります。他の問題は、I/Oは仮想メモリを取り扱うために消費する時間の一部(残りはスワップファイルの入出力時のデータのエンコード/デコードにかかるCPU時間)を占めているということです。このため、私は3番目の選択肢を選ぶことにしました。
  1. I/Oスレッドを使用します。スワップの入出力操作を行うための、スレッドプールを用意します。Redis仮想メモリの実装で利用されているのは、これです。それでは、この仕組の動きの詳細を説明します。

I/Oスレッド

スレッド化された仮想メモリは、次のような目標を掲げて設計されました。重要度順になっています。

  • シンプルな実装。競合状態が少なく、ロックがシンプルで、仮想メモリのシステムがなるべく完全に他のRedisコードと疎になる。
  • 良いパフォーマンス。メモリ内の値にクライアントがアクセスする場合に、ロックされない。
  • オブジェクトのデコード/エンコードはI/Oスレッド上で行う

このような目標を目指して実装したところ、Redisのメインスレッドと、I/Oスレッドがキューと、1つのミューテックスを使ってジョブのやりとりをする、という実装になりました。基本的には、メインスレッドが、バックグラウンドのI/Oスレッドにお願いしたい仕事を持った場合、I/Oジョブ構造体を、 server.io_newjobs キュー(単なるリンクドリスト)に積みます。アクティブなI/Oスレッドがなければ、スレッドを起動します。この時に、I/OスレッドがI/Oジョブを処理して、 server.io_processed キューに結果を積みます。I/Oスレッドは、UNIXパイプにデータを送ることによって、メインスレッドに対して新しいジョブが実行され、処理が終わったことを通知します。

iojob 構造体は次のような実装になっています。

typedef struct iojob {
    int type;   /* リクエストタイプ, REDIS_IOJOB_* */
    redisDb *db;/* Redisデータベース*/
    robj *key;  /* どのキーをスワップするI/Oリクエストか? */
    robj *val;  /* REDIS_IOREQ_*_SWAPコマンドによって処理される値オブジェクト。
                 * もしくは、REDIS_IOREQ_LOADの処理を行うI/Oスレッド
                 * がこの変数に値を設定する。 */
    off_t page; /* オブジェクトの読み/書きを行うページ番号 */
    off_t pages; /* オブジェクトを保存するのに必要なページ数。PREPARE_SWAPの返り値 */
    int canceled; /* ブロッキング仮想メモリが処理をキャンセルしたいときに、
                   * 値を設定する。 */
    pthread_t thread; /* このエントリーを処理する、スレッドのID */
} iojob;

I/Oスレッドによって実行可能なジョブは、次の3種類あります。これは type 属性で設定されます。

REDIS_IOJOB_LOAD
与えられたキーの値を、スワップファイルからメモリに読み込みます。スワップファイル内の位置は page 属性に、オブジェクトの種類は key->vtype に格納されています。処理の結果は、構造体の val 属性に格納されます。
REDIS_IOJOB_PREPARE_SWAP
val 属性に格納されているオブジェクトをスワップに保存するために、必要なページ数を計算します。処理の結果は pages フィールドに格納されます。
REDIS_IOJOB_DO_SWAP
val 属性に格納されているオブジェクトを、スワップファイルの page で指定されたオフセットのページに送ります。

メインスレッドは、上記の3つのタスクだけを委譲します。スワップファイルの格納する場所を探したり、スワップするオブジェクトを決定したり、Redisオブジェクトの storage 属性の値に、現在の状態を反映したりといった残りの処理はすべてメインスレッド自身が行います。

ブロッキング仮想メモリの確率論的拡張としてのノンブロッキング仮想メモリ

ここまでのところで、処理の重い仮想メモリの操作を、バックグラウンドジョブとして処理できるようになりました。どのようにして、メインスレッドで行う他の処理と歩調を合わせて行くのでしょうか?ブロッキング仮想メモリの場合、検索している時にオブジェクトがスワップアウトされていることに気づきますが、これでは遅すぎます。C言語では、コルーチンや継続がないため、コマンド処理の途中でバックグラウンドのジョブを起動して、関数の実行を中断し、I/Oスレッドの処理が終わったタイミングで中断したポイントから処理を再開するということは簡単ではありません。

再話、もっと簡単な方法がありました。私たちはシンプルな方が好きです。仮想メモリの実装は、基本的にブロッキング仮想メモリと考えますが、ブロッキングが発生していないように見えるように最適化します。

行っていることは次の通りです。

  • クライアントからコマンドが送信されるたびに、コマンド実行前に、コマンドの引き数リストに含まれるキーが、スワップされたものではないか確認します。Redisのコマンドの形式がシンプルであるため、コマンドごとに、どの引き数がキーを表しているかは知っています。
  • もし、要求されたコマンドで渡されたキーがスワップされていることを検出したら、コマンドを実行する代わりに一端クライアントをブロックします。スワップされた値に関連するキーごとに、メモリに戻すためのI/Oジョブが作られます。この間も、ブロックされたクライアントのことは気にしないで、メインスレッドのイベントループの実行は継続されます。
  • その間、I/Oスレッドはメモリの値をロードします。I/Oスレッドが値をロードし終えたら、UNIXパイプに1バイトのデータを送信します。vmThreadedIOCompletedJob関数の中で、パイプファイルディスクリプタは、メインスレッドのイベントループに関連付けられた、読み込み可能なイベントを保持します。もしこの関数が、ブロックされたクライアントに必要なすべての値の読み込みを検知したら、クライアントの起動は再開し、元のコマンドを呼び出します。

スワップされた値を使うコマンドが発生すると、値がロードされるまではクライアントの動作が一時停止するため、正しいキーがメモリ上置かれているブロッキング仮想メモリとほぼ同じように考えることができます。

どの引き数がキーかを調べる関数が失敗しても問題はありません。ルックアップの関数は与えられたキーの値がスワップされていることを気づいて、ブロックしてそれをロードしにいきます。そのため、利用しようとしたキーが利用できない場合には、ブロッキング仮想メモリに戻ります。

たとえば、 GETBY オプション付きの SORT コマンドの場合、どのキーが必要となるかを事前に把握することが困難なため、少なくとも最初の実装では、 SORT BY/GET の実行はブロッキング仮想メモリとして実行されます。

クライアントのブロック

どのようにクライアントをブロックしているのでしょうか?サーバ上のイベントループで、クライアントを一時停止させるのはとても簡単です。読み込みハンドラをキャンセルします。例えば、 BLPOP のようなコマンドの場合は、これとは異なり、新しいデータを処理(新しいデータを入力バッファ積む)するのではなく、単にブロックしているとクライアントにマークを付けるだけの場合もあります。

I/Oジョブの中断

ブロッキング仮想メモリと、ノンブロッキング仮想メモリの間でインタラクションすることは、簡単ではありません。ノンブロッキング命令と、ブロッキング命令が同じキーに対して同時に発生すると何が起きるでしょうか?

例えば、 SORT BY が実行されていると、いくつかのキーは SORT コマンドの流儀に従って、ブロッキング仮想メモリの仕組みをつかってロードされます。これと同時に、同じキーに対して、値をスワップからロードする場合にI/Oジョブを作って行う GET コマンドが他のクライアントから呼ばれたとします。

この問題を解決する唯一シンプルな方法は、メインスレッドからI/Oジョブをkillできるようにすることです。もしブロッキング仮想メモリキーをロードしたり、スワップしたい場合には、 REDIS_VM_LOADINGREDIS_VM_SWAPPING といったフラグを設定します。ここで、このキーに関するI/Oジョブをkillして、実行したいブロッキング操作を行います。

これは言うほど簡単ではありません。これを行おうとした瞬間、I/Oジョブは次の3つのうち、どれかの状態になります。

  • server.io_newjobs: ジョブがキューに入れられただけの状態で、スレッドはまだ処理していません。
  • server.io_processing: ジョブは今現在、I/Oスレッドによって処理されています。
  • server.io_processed: ジョブはすでに完了しています。

I/Oジョブは、 vmCancelThreadedIOJob を使ってkillすることができます。

  • Jobがまだにnewjobsのキューに格納されている状態であれば、スレッドはこのジョブの処理をまったく行っていないため、キューからiojob構造体を削除するだけでkillすることができます。
  • もしジョブがprocessingキューにある場合は、スレッドが我々の仕事に干渉してきています。関連するオブジェクトに対して処理を行っている可能性もあります。ここで行えることは、ブロッキングされた方法で、次のキューに移動されるのを待つことだけです。幸い、このようなことが起きる確率は極めて低いため、パフォーマンス上の問題となることはありません。
  • もし、ジョブがprocessedキューにある場合は、iojob構造体の canceled 属性に1をセットし、キャンセルされたとするマークをつけます。これにより、完了したジョブが実行されるぜに、無視されるようになり、ジョブの構造体が開放されるようになります。