英語フランス語スペイン語

OnWorksファビコン

perlthrtut-クラウドでのオンライン

Ubuntu Online、Fedora Online、Windows Onlineエミュレーター、またはMACOSオンラインエミュレーターを介してOnWorks無料ホスティングプロバイダーでperlthrtutを実行します

これは、Ubuntu Online、Fedora Online、Windowsオンラインエミュレーター、MACOSオンラインエミュレーターなどの複数の無料オンラインワークステーションのXNUMXつを使用してOnWorks無料ホスティングプロバイダーで実行できるコマンドperlthrtutです。

プログラム:

NAME


perlthrtut-Perlのスレッドに関するチュートリアル

DESCRIPTION


このチュートリアルでは、Perlインタープリタースレッド(と呼ばれることもあります)の使用について説明します。
iスレッド)。 このモデルでは、各スレッドは独自のPerlインタープリターと任意のデータで実行されます
スレッド間の共有は明示的である必要があります。 のユーザーレベルのインターフェイス iスレッド 使用
スレッドクラス。

注意:5.005モデルと呼ばれる別の古いPerlスレッドフレーバーがありました。
スレッドクラス。 この古いモデルには問題があることが知られており、廃止され、削除されました
リリース5.10の場合。 既存の5.005スレッドコードを移行することを強くお勧めします
できるだけ早く新しいモデルに。

「perl-V」を実行して見ることで、どのスレッドフレーバー(またはどちらでもない)を確認できます。
「プラットフォーム」セクションで。 「useithreads=define」がある場合はithreadsがあり、
「use5005threads=define」には5.005スレッドがあります。 どちらも持っていない場合は持っていません
スレッドサポートが組み込まれています。両方がある場合は、問題が発生します。

スレッドとthreads::sharedモジュールは、コアPerlディストリビューションに含まれています。
さらに、それらはCPANで個別のモジュールとして維持されるため、そこで確認できます
任意のアップデートのために。

この試験は Is A スレッド とにかく?


スレッドは、単一の実行ポイントを持つプログラムを介した制御の流れです。

プロセスのようにひどく聞こえますね。 まあ、そうすべきです。 スレッドはのXNUMXつです
プロセスの一部。 すべてのプロセスには少なくともXNUMXつのスレッドがあり、これまではすべてのプロセスに
Perlを実行しているプロセスにはスレッドが5.8つしかありませんでした。 ただし、XNUMXでは、追加のスレッドを作成できます。
どのように、いつ、そしてなぜかをお見せします。

ねじ付き 演奏曲目 Models


スレッド化されたプログラムを構成する基本的な方法はXNUMXつあります。 どのモデルあなた
選択は、プログラムで何をする必要があるかによって異なります。 多くの重要なスレッドの場合
プログラムでは、プログラムのさまざまな部分にさまざまなモデルを選択する必要があります。

上司/労働者
ボス/ワーカーモデルには通常XNUMXつあります ボス スレッドとXNUMXつ以上 worker スレッド。 は
ボススレッドは、実行する必要のあるタスクを収集または生成してから、それらのタスクを分割します
適切なワーカースレッドに。

このモデルは、メインスレッドが何らかのイベントを待機するGUIおよびサーバープログラムで一般的です。
次に、そのイベントを適切なワーカースレッドに渡して処理します。 一度
イベントが渡されると、ボススレッドは別のイベントの待機に戻ります。

上糸は比較的小さな仕事をします。 タスクは必ずしも実行されるとは限りませんが
他のどの方法よりも高速であるため、ユーザーの応答時間が最も長くなる傾向があります。

Work 乗組員
作業員モデルでは、基本的に同じことを行ういくつかのスレッドが作成されます
さまざまなデータ。 これは、古典的な並列処理とベクトルを厳密に反映しています
プロセッサ。多数のプロセッサが、多くのプロセッサに対してまったく同じことを行います。
データ。

このモデルは、プログラムを実行しているシステムが配布する場合に特に役立ちます
異なるプロセッサにまたがる複数のスレッド。 レイトレーシングや
レンダリングエンジン。個々のスレッドが中間結果を渡して、
ユーザーの視覚的フィードバック。

パイプライン
パイプラインモデルは、タスクを一連のステップに分割し、XNUMXつの結果を渡します
次のスレッド処理に進みます。 各スレッドは、の各部分に対してXNUMXつのことを行います
データを取得し、結果を次のスレッドに渡します。

このモデルは、複数のプロセッサがある場合に最も理にかなっているため、XNUMXつ以上のスレッドがあります
並行して実行されますが、他のコンテキストでも意味をなすことがよくあります。
個々のタスクを小さくシンプルに保つだけでなく、
他の部分が進行している間、ブロックするパイプライン(たとえば、I / Oまたはシステムコール)。
パイプラインのさまざまな部分をさまざまなプロセッサで実行している場合は、
各プロセッサのキャッシュを利用します。

このモデルは、再帰プログラミングの形式にも便利です。
サブルーチンはそれ自体を呼び出し、代わりに別のスレッドを作成します。 プライムおよびフィボナッチ素数ジェネレーター
どちらもこの形式のパイプラインモデルにうまく対応しています。 (素数ジェネレーターのバージョン
後で提示されます。)

この試験は 種類 of スレッド   パール スレッド?


他のスレッド実装の経験がある場合は、そのことがわかるかもしれません
あなたが期待するものではありません。 Perlを扱うときは覚えておくことが非常に重要です
そのスレッド パール スレッド です。 もしアカウントが違う場合: X スレッド Xのすべての値に対して。POSIXではありません。
スレッド、またはDecThreads、またはJavaのグリーンスレッド、またはWin32スレッド。 がある
類似点、そして広い概念は同じですが、あなたが探し始めたら
実装の詳細については、がっかりしたり混乱したりします。 おそらく両方。

これは、Perlスレッドがこれまでのすべてのものと完全に異なるということではありません
の前に来る。 彼らはそうではありません。 Perlのスレッドモデルは、他のスレッドモデルに多くを負っています。
特にPOSIX。 ただし、PerlがCではないのと同様に、PerlスレッドはPOSIXスレッドではありません。 それで
ミューテックスまたはスレッドの優先順位を探していることに気付いた場合は、一歩下がってください。
あなたが何をしたいのか、そしてPerlがそれをどのように行うことができるのかを少し考えてみてください。

ただし、Perlスレッドは魔法のように物事を行うことができないことを覚えておくことが重要です。
オペレーティングシステムのスレッドで許可されています。 したがって、システムがプロセス全体をブロックする場合
「sleep()」、Perlも通常そうします。

パール スレッド です。 違います。

スレッドセーフ モジュール


スレッドの追加により、Perlの内部が大幅に変更されました。 影響があります
XSコードまたは外部ライブラリを使用してモジュールを作成する人向け。 ただし、Perlデータ以降
デフォルトではスレッド間で共有されないため、Perlモジュールはスレッドになる可能性が高くなります-
安全であるか、簡単にスレッドセーフにすることができます。 スレッドセーフとしてタグ付けされていないモジュールは、
本番コードで使用する前に、テストまたはコードレビューを行ってください。

使用する可能性のあるすべてのモジュールがスレッドセーフであるとは限らないため、常にモジュールを想定する必要があります
ドキュメントに別段の記載がない限り、安全ではありません。 これには、次のモジュールが含まれます
コアの一部として配布されます。 スレッドは比較的新しい機能であり、
標準モジュールはスレッドセーフではありません。

モジュールがスレッドセーフであっても、モジュールが適切に機能するように最適化されているわけではありません。
スレッド付き。 モジュールは、スレッド化された新機能を利用するように書き直される可能性があります
スレッド環境でのパフォーマンスを向上させるPerl。

何らかの理由でスレッドセーフではないモジュールを使用している場合は、自分自身を保護することができます
XNUMXつのスレッドからそれを使用することによって、そしてまったくXNUMXつのスレッドだけ。 アクセスするために複数のスレッドが必要な場合
このようなモジュールでは、セマフォと多くのプログラミング分野を使用してアクセスを制御できます
それに。 セマフォについては、「基本的なセマフォ」で説明しています。

「システムライブラリのスレッドセーフ」も参照してください。

スレッド の基礎


スレッドモジュールは、スレッドプログラムを作成するために必要な基本機能を提供します。 の
次のセクションでは、基本をカバーし、作成するために何をする必要があるかを示します
スレッド化されたプログラム。 その後、スレッドモジュールの機能のいくつかを見ていきます
スレッドプログラミングが簡単になります。

Basic スレッド サポート
スレッドのサポートは、Perlのコンパイル時オプションです。 それはいつオンまたはオフになるものです
Perlは、プログラムがコンパイルされるときではなく、サイトで構築されます。 Perlの場合
スレッドサポートを有効にしてコンパイルされていない場合、スレッドを使用しようとすると失敗します。

プログラムは、Configモジュールを使用して、スレッドが有効になっているかどうかを確認できます。 もしあなたの
プログラムはそれらなしでは実行できません。次のように言うことができます。

Configを使用します。
$Config{useithreads}または
die('このプログラムを実行するためにスレッドでPerlを再コンパイルします。');

スレッド化されている可能性のあるモジュールを使用しているスレッド化されている可能性のあるプログラムには、次のようなコードが含まれている可能性があります。

Configを使用します。
MyModを使用します。

ベギン {
if($ Config {useithreads}){
#スレッドがあります
MyMod_threadedが必要です。
MyMod_threadedをインポートします。
場合} else {
MyMod_unthreadedが必要です。
MyMod_unthreadedをインポートします。
}
}

スレッドありとスレッドなしの両方で実行されるコードは通常かなり厄介なので、
スレッド固有のコードを独自のモジュールに分離します。 上記の例では、それが
「MyMod_threaded」はであり、スレッド化されたPerlで実行している場合にのみインポートされます。

A 注意 自己紹介  
実際の状況では、すべてのスレッドが前に実行を終了するように注意する必要があります
プログラムは終了します。 そのケアは これらの例では、
シンプルさ。 これらの例を実行する as is 通常、次の原因で発生するエラーメッセージが生成されます
プログラムの終了時にまだスレッドが実行されているという事実。 あなたはすべきではありません
これに驚いた。

作成 スレッド
スレッドモジュールは、新しいスレッドを作成するために必要なツールを提供します。 他のように
モジュールの場合、Perlにそれを使用することを伝える必要があります。 「スレッドを使用する;」 すべてをインポートします
基本的なスレッドを作成するために必要な部分。

スレッドを作成する最も簡単で簡単な方法は、「c​​reate()」を使用することです。

スレッドを使用します。

私の$thr=スレッド->create(\&sub1);

サブサブ1 {
print("スレッド内\n");
}

「create()」メソッドは、サブルーチンへの参照を取得し、次のような新しいスレッドを作成します。
参照されたサブルーチンで実行を開始します。 次に、制御は両方をサブルーチンに渡します
と発信者。

必要に応じて、プログラムはスレッドの一部としてパラメータをサブルーチンに渡すことができます
起動。 「threads->create()」呼び出しの一部としてパラメータのリストを含めるだけです。
このような:

スレッドを使用します。

私の$Param3='foo';
私の$thr1=スレッド->create(\&sub1、'Param 1'、'Param 2'、$ Param3);
私の@ParamList=(42、'こんにちは'、3.14);
私の$thr2=スレッド->create(\&sub1、@ ParamList);
私の$thr3=スレッド->create(\&sub1、qw(Param1 Param2 Param3));

サブサブ1 {
私の@InboundParameters=@_;
print("スレッド内\n");
print('パラメータを取得>'、join('<>'、@ InboundParameters)、 "<\ n");
}

最後の例は、スレッドの別の機能を示しています。 あなたはいくつかをスポーンすることができます
同じサブルーチンを使用するスレッド。 各スレッドは同じサブルーチンを実行しますが、
別の環境と潜在的に別の引数を持つ別のスレッド。

「new()」は「create()」の同義語です。

待っています A スレッド 出口
スレッドもサブルーチンであるため、値を返すことができます。 スレッドが終了するのを待つ
返される可能性のある値を抽出するには、「join()」メソッドを使用できます。

スレッドを使用します。

my($ thr)=スレッド-> create(\&sub1);

私の@ReturnData=$ thr-> join();
print('スレッドが返されました'、join('、'、@ReturnData)、 "\ n");

sub sub1 {return('2'、'foo'、XNUMX); }

上記の例では、「join()」メソッドはスレッドが終了するとすぐに戻ります。 加えて
スレッドが終了するのを待って、スレッドが持つ可能性のある値を収集します
返された「join()」は、スレッドに必要なOSのクリーンアップも実行します。 そのクリーンアップ
特に、大量のスレッドを生成する長時間実行されるプログラムの場合は、重要になる可能性があります。 もしも
戻り値が必要なく、スレッドが終了するのを待ちたくない場合は、
次に説明するように、代わりに「detach()」メソッドを呼び出す必要があります。

注:上記の例では、スレッドはリストを返すため、スレッドが
作成呼び出しは、リストコンテキスト(つまり、「my($ thr)」)で行われます。 「$thr->」を参照してくださいjoin()"スレッドで
スレッドコンテキストと戻り値の詳細については、スレッドの「THREADCONTEXT」を参照してください。

無視する A スレッド
「join()」はXNUMXつのことを行います。スレッドが終了するのを待ち、スレッドが終了した後、クリーンアップして戻ります。
スレッドが生成した可能性のあるデータ。 しかし、スレッドに興味がない場合はどうなりますか
戻り値、そしてスレッドがいつ終了するかは本当に気にしませんか? あなたが望むのは
それが終わった後にクリーンアップされるスレッド。

この場合、「detach()」メソッドを使用します。 スレッドが切り離されると、それはまで実行されます
完了した; その後、Perlは自動的にクリーンアップします。

スレッドを使用します。

私の$thr=スレッド->create(\&sub1); #スレッドを生成します

$ thr-> detach(); #今では公式にはもう気にしません

眠る(15); #スレッドをしばらく実行させます

サブサブ1 {
私の$ count = 0;
while(1){
$ count ++;
print( "\ $ count is $ count \ n");
眠る(1);
}
}

スレッドが切り離されると、そのスレッドは結合されない可能性があり、スレッドが持つ可能性のあるデータが返される可能性があります
生成されたもの(実行されて参加を待機している場合)は失われます。

「detach()」は、スレッドがそれ自体をデタッチできるようにするクラスメソッドとして呼び出すこともできます。

スレッドを使用します。

私の$thr=スレッド->create(\&sub1);

サブサブ1 {
スレッド->detach();
#もっと仕事をする
}

プロセス スレッド 終了
スレッドでは、すべてのスレッドが完了するまで実行される可能性があることを確認するように注意する必要があります。
それがあなたが望むものであると仮定します。

プロセスを終了するアクションは終了します 実行中のスレッド。 死にます()出口()
このプロパティがあり、perlはメインスレッドが終了するときに、おそらく暗黙的に終了します
たとえそれがあなたが望むものでなくても、あなたのコードの終わりから落ちることによって。

この場合の例として、このコードは「Perlはアクティブなスレッドで終了しました:
2実行中および結合されていません」:

スレッドを使用します。
私の$thr1=スレッド->new(\&thrsub、 "test1");
私の$thr2=スレッド->new(\&thrsub、 "test2");
サブ thrsub {
my($ message)= @_;
睡眠1;
印刷"スレッド$メッセージ\n";
}

ただし、最後に次の行が追加された場合:

$ thr1-> join();
$ thr2-> join();

XNUMX行の出力が出力され、おそらくより有用な結果になります。

スレッド Rescale データ


スレッドの基本について説明したので、次のトピックであるデータについて説明します。
スレッド化は、スレッド化されていないプログラムのデータアクセスにいくつかの複雑さをもたらします
心配する必要はありません。

共有 非共有 Rescale データ
Perlの最大の違い iスレッド および古い5.005スタイルのスレッド、または
重要なのは、他のほとんどのスレッドシステムにとって、デフォルトではデータがないということです。
共有。 新しいPerlスレッドが作成されると、現在のスレッドに関連付けられているすべてのデータ
は新しいスレッドにコピーされ、その後その新しいスレッドにプライベートになります! これは
この場合、
データは、同じプロセス内のメモリの別の部分にコピーされるだけで、
本物のフォークが起こっています。

ただし、スレッド化を利用するには、通常、スレッドが少なくとも一部を共有する必要があります。
それらの間のデータ。 これは、threads :: sharedモジュールと「:shared」を使用して行われます。
属性:

スレッドを使用します。
スレッドを使用する::共有;

私の$foo:shared = 1;
私の$bar= 1;
スレッド->create(sub {$ foo ++; $ bar ++;})-> join();

print( "$ foo \ n"); #$ fooが共有されているため、2を出力します
print( "$ bar \ n"); #$ barが共有されていないため、1を出力します

共有配列の場合、配列のすべての要素が共有され、共有ハッシュの場合、
すべてのキーと値が共有されます。 これにより、何に割り当てることができるかが制限されます
共有配列およびハッシュ要素:単純な値または共有変数への参照のみが
許可-これは、プライベート変数が誤って共有されることがないようにするためです。 悪い
割り当てにより、スレッドが停止します。 例えば:

スレッドを使用します。
スレッドを使用する::共有;

私の$var= 1;
私の$svar:shared = 2;
私の%hash:shared;

...いくつかのスレッドを作成します..。

$ hash {a} = 1; #すべてのスレッドはexists($ hash {a})を参照
#および$ hash {a} == 1
$ hash {a} = $ var; #大丈夫-値によるコピー:前と同じ効果
$ hash {a} = $ svar; #大丈夫-値によるコピー:前と同じ効果
$ hash {a} = \ $ svar; #OK-共有変数への参照
$ hash {a} = \ $ var; #これは死ぬ
delete($ hash {a}); #わかりました-すべてのスレッドに!exists($ hash {a})が表示されます

共有変数は、XNUMXつ以上のスレッドがでそれを変更しようとした場合に保証することに注意してください
同時に、変数の内部状態が破損することはありません。 しかし、そこに
次のセクションで説明するように、これを超える保証はありません。

スレッド 落とし穴: 競馬
スレッドは便利なツールの新しいセットをもたらしますが、多くの落とし穴ももたらします。 XNUMX
落とし穴は競合状態です:

スレッドを使用します。
スレッドを使用する::共有;

私の$x:shared = 1;
私の$thr1=スレッド->create(\&sub1);
私の$thr2=スレッド->create(\&sub2);

$ thr1-> join();
$ thr2-> join();
print( "$ x \ n");

sub sub1 {my $ foo = $ x; $ x = $ foo + 1; }
subsub2{私の$bar= $ x; $ x = $ bar + 1; }

$ xはどうなると思いますか? 残念ながら、その答えは次のとおりです。 it 依存。 「sub1()」と
「sub2()」は、グローバル変数$ xにアクセスします。XNUMX回は読み取り、もうXNUMX回は書き込みです。 応じて
スレッド実装のスケジューリングアルゴリズムからフェーズのフェーズに至るまでの要因
月、$xは2または3にすることができます。

競合状態は、共有データへの非同期アクセスによって引き起こされます。 明示的なし
同期、共有データに何も起こらなかったことを確認する方法はありません
アクセスしてから更新するまでの間に。 この単純なコードフラグメントでさえ
エラーの可能性があります:

スレッドを使用します。
私の$x:shared = 2;
私の$y:shared;
私の$z:shared;
私の$thr1=スレッド->create(sub {$ y = $ x; $ x = $ y + 1;});
私の$thr2=スレッド->create(sub {$ z = $ x; $ x = $ z + 1;});
$ thr1-> join();
$ thr2-> join();

XNUMXつのスレッドは両方とも$xにアクセスします。 各スレッドは、いつでも中断される可能性があります。
任意の順序で実行されます。 最後に、$ xは3または4になり、$yと$zは両方とも2になります。
または3。

「$x+=5」または「$x++」でさえ、アトミックであることが保証されていません。

プログラムが他のスレッドからアクセスできるデータまたはリソースにアクセスするときはいつでも、
アクセスまたはリスクデータの不整合と競合状態を調整するための手順を実行する必要があります。
Perlは競合状態から内部を保護しますが、保護しないことに注意してください
あなたからあなた。

同期 コントロール


Perlは、Perlとの間の相互作用を調整するための多くのメカニズムを提供します
競合状態などを回避するためのデータ。 これらのいくつかは似ているように設計されています
「pthreads」などのスレッドライブラリで使用される一般的な手法。 その他はPerl-
明確。 多くの場合、標準的な手法は不器用で正しく理解するのが困難です(
条件は待機します)。 可能な場合は、通常、次のようなPerlishテクニックを使用する方が簡単です。
キュー。これにより、関連するハードワークの一部が削除されます。

制御 アクセス: ロック()
「lock()」関数は共有変数を受け取り、それにロックをかけます。 他のスレッドはできません
ロックを保持しているスレッドによって変数のロックが解除されるまで、変数をロックします。 ロックを解除する
ロックスレッドがへの呼び出しを含むブロックを出ると自動的に発生します
「lock()」関数。 「lock()」の使用は簡単です。この例にはいくつかあります
いくつかの計算を並行して実行し、時折現在の合計を更新するスレッド:

スレッドを使用します。
スレッドを使用する::共有;

私の$total:shared = 0;

サブ計算{
while(1){
私の$result;
#(...いくつかの計算を行い、$ resultを設定します...)
{
lock($ total); #ロックを取得するまでブロックする
$ total + = $ result;
}#スコープの最後で暗黙的に解放されたロック
$ result==0の場合は最後。
}
}

私の$thr1=スレッド->create(\&calc);
私の$thr2=スレッド->create(\&calc);
私の$thr3=スレッド->create(\&calc);
$ thr1-> join();
$ thr2-> join();
$ thr3-> join();
print( "total = $ total \ n");

「lock()」は、ロックされている変数が使用可能になるまでスレッドをブロックします。 「lock()」の場合
が返されると、スレッドは、他のスレッドがその変数をロックできないことを確認できます。
ロックを含むブロックが終了します。

ロックは問題の変数へのアクセスを妨げるのではなく、
ロックの試行。 これは、Perlの長年の礼儀正しい伝統と一致しています
プログラミング、および「flock()」が提供するアドバイザリファイルロック。

スカラーだけでなく、配列とハッシュもロックできます。 ただし、アレイをロックしても、
配列要素に対する後続のロックをブロックします。配列自体に対するロックの試行だけをブロックします。

ロックは再帰的です。つまり、スレッドが変数を複数回ロックしても問題ありません。
ロックは、変数の最も外側の「lock()」がスコープから外れるまで続きます。 にとって
例:

私の$x:shared;
やれ();

サブドット {
{
{
lock($ x); #ロックを待つ
lock($ x); #NOOP-私たちはすでにロックを持っています
{
lock($ x); #NOOP
{
lock($ x); #NOOP
lockit_some_more();
}
}
}#***ここで暗黙のロック解除***
}
}

サブlockit_some_more {
lock($ x); #NOOP
}#ここでは何も起こりません

「unlock()」関数がないことに注意してください-変数のロックを解除する唯一の方法は、許可することです
範囲外になります。

ロックは、ロックされている変数に含まれるデータを保護するために使用するか、または
コードのセクションなど、他の何かを保護するために使用できます。 この後者の場合、
問題の変数は有用なデータを保持しておらず、目的のためにのみ存在します
ロックされています。 この点で、変数はミューテックスや基本的なセマフォのように動作します
従来のスレッドライブラリの。

A スレッド 落とし穴: デッドロック
ロックはデータへのアクセスを同期するための便利なツールであり、適切に使用することが重要です
安全な共有データに。 残念ながら、特にロックには危険が伴います。
複数のロックが関係しています。 次のコードを検討してください。

スレッドを使用します。

私の$x:shared = 4;
私の$y:shared ='foo';
私の$thr1=スレッド->create(sub {
lock($ x);
眠る(20);
lock($ y);
});
私の$thr2=スレッド->create(sub {
lock($ y);
眠る(20);
lock($ x);
});

このプログラムは、あなたがそれを殺すまでおそらくハングします。 ハングしない唯一の方法は、
XNUMXつのスレッドのうち、最初に両方のロックを取得します。 ハングすることが保証されているバージョンはもっと
複雑ですが、原理は同じです。

最初のスレッドは$xのロックを取得し、その後、一時停止した後、XNUMX番目のスレッドが
スレッドはおそらくいくつかの作業を行う時間がありました。$yのロックを取得してみてください。 一方、
XNUMX番目のスレッドは$yのロックを取得し、後で$xのロックを取得しようとします。 XNUMX番目のロック
両方のスレッドの試行はブロックされ、それぞれが他方がロックを解放するのを待ちます。

この状態はデッドロックと呼ばれ、XNUMXつ以上のスレッドが試行するたびに発生します
他の人が所有するリソースをロックするため。 各スレッドはブロックされ、
その他、リソースのロックを解除します。 しかし、それは決して起こりません。
リソース自体は、ロックが解放されるのを待っています。

この種の問題を処理する方法はいくつかあります。 最良の方法は常に持っていることです
すべてのスレッドは、まったく同じ順序でロックを取得します。 たとえば、変数をロックする場合
$ x、$ y、および$ zは、常に$yの前に$xをロックし、$zの前に$yをロックします。 保持するのも最善です
デッドロックのリスクを最小限に抑えるために、短時間だけロックします。

以下で説明する他の同期プリミティブは、同様の問題に悩まされる可能性があります。

キュー: 通過 Rescale データ
キューは、データを一方の端に入れて取り出すことができる特別なスレッドセーフオブジェクトです。
もうXNUMXつは、同期の問題を心配する必要はありません。 彼らはきれいです
簡単で、次のようになります。

スレッドを使用します。
Thread::Queueを使用します。

私の$DataQueue= Thread :: Queue-> new();
私の$thr=スレッド->create(sub {
while(my $ DataElement = $ DataQueue-> dequeue()){
print("キューから$DataElementをポップしました\n");
}
});

$ DataQueue->エンキュー(12);
$ DataQueue-> enqueue( "A"、 "B"、 "C");
眠る(10);
$ DataQueue-> enqueue(undef);
$ thr-> join();

「Thread::Queue-> new()」を使用してキューを作成します。 次に、スカラーのリストをに追加できます
最後は「enqueue()」で終わり、「dequeue()」でスカラーを先頭からポップします。 キュー
固定サイズはなく、必要に応じて成長して、すべてを押し込むことができます。

キューが空の場合、「dequeue()」は別のスレッドが何かをエンキューするまでブロックします。 これ
キューは、イベントループやスレッド間のその他の通信に最適です。

セマフォ: 同期しています Rescale データ アクセス
セマフォは一種の一般的なロックメカニズムです。 最も基本的な形式では、動作します
ロック可能なスカラーと非常によく似ていますが、データを保持できないことと、データを保持する必要があることを除けば
明示的にロック解除されました。 高度な形式では、一種のカウンターのように機能し、
複数のスレッドに ロック いつでも。

Basic セマフォ
セマフォには、「down()」と「up()」のXNUMXつのメソッドがあります。「down()」はリソース数を減らします。
「up()」はそれをインクリメントします。 「down()」の呼び出しは、セマフォの現在のカウントがブロックされる場合
ゼロ未満にデクリメントします。 このプログラムは簡単なデモンストレーションを提供します:

スレッドを使用します。
Thread::Semaphoreを使用します。

私の$semaphore= Thread :: Semaphore-> new();
私の$GlobalVariable:shared = 0;

$ thr1=スレッド->create(\&sample_sub、1);
$ thr2=スレッド->create(\&sample_sub、2);
$ thr3=スレッド->create(\&sample_sub、3);

サブサンプル_サブ{
私の$SubNumber= shift(@_);
私の$TryCount= 10;
私の$LocalCopy;
眠る(1);
while($ TryCount--){
$ semaphore-> down();
$ LocalCopy = $ GlobalVariable;
print("$TryCountはサブ$SubNumberに対して残りを試行します"
。"(\ $GlobalVariableは$GlobalVariableです)\ n");
眠る(2);
$ LocalCopy ++;
$ GlobalVariable = $ LocalCopy;
$ semaphore-> up();
}
}

$ thr1-> join();
$ thr2-> join();
$ thr3-> join();

サブルーチンのXNUMXつの呼び出しは、すべて同期して動作します。 ただし、セマフォは
一度にXNUMXつのスレッドだけがグローバル変数にアクセスしていることを確認してください。

高機能 セマフォ
デフォルトでは、セマフォはロックのように動作し、一度にXNUMXつのスレッドのみを「down()」させます。
ただし、セマフォには他にも用途があります。

各セマフォにはカウンターが付いています。 デフォルトでは、セマフォは
カウンターをXNUMXに設定すると、「down()」はカウンターをXNUMXつデクリメントし、「up()」はXNUMXつインクリメントします。
ただし、異なるものを渡すだけで、これらのデフォルトの一部またはすべてをオーバーライドできます。
値:

スレッドを使用します。
Thread::Semaphoreを使用します。

私の$semaphore= Thread :: Semaphore->新製品(5);
#カウンターをXNUMXに設定してセマフォを作成します

私の$thr1=スレッド->create(\&sub1);
私の$thr2=スレッド->create(\&sub1);

サブサブ1 {
$セマフォ->ダウン(5); #カウンターをXNUMXつ減らします
#ここで何かをする
$セマフォ->up(5); #カウンターをXNUMXつ増やします
}

$ thr1-> detach();
$ thr2-> detach();

「down()」がカウンターをゼロ未満にデクリメントしようとすると、カウンターがゼロになるまでブロックされます。
十分大きい。 セマフォは開始カウントがゼロで作成できますが、
「up()」または「down()」は常にカウンターを少なくともXNUMXつ変更します。したがって、
"$セマフォ->ダウン(0)」は「$semaphore->」と同じですダウン(1)」

もちろん、問題は、なぜこのようなことをするのかということです。 セマフォを作成する理由
開始カウントがXNUMXではない場合、またはなぜそれを複数デクリメントまたはインクリメントするのですか?
答えはリソースの可用性です。 アクセスを管理したい多くのリソース
一度に複数のスレッドで安全に使用できます。

たとえば、GUI駆動のプログラムを考えてみましょう。 それはそれが使用するセマフォを持っています
ディスプレイへのアクセスを同期するため、一度にXNUMXつのスレッドのみが描画されます。 便利ですが
もちろん、適切に設定されるまで、スレッドが描画を開始することは望ましくありません。 の
この場合、カウンターをゼロに設定してセマフォを作成し、状況に応じてそれを上げることができます。
描画の準備ができています。

カウンターがXNUMXより大きいセマフォも、クォータを確立するのに役立ちます。 言う、
たとえば、一度にI/Oを実行できるスレッドが多数あるとします。 あなたはしたくない
ただし、すべてのスレッドが一度に読み取りまたは書き込みを行うと、
I / Oチャネル、またはプロセスのファイルハンドルのクォータを使い果たします。 セマフォを使用できます
任意の時点で必要な同時I/O要求(または開いているファイル)の数に初期化されます
一度、スレッドを静かにブロックおよびブロック解除します。

スレッドがチェックアウトする必要がある場合は、より大きな増分または減分が便利です。
または、一度に多数のリソースを返します。

待っています for a 調子
関数「cond_wait()」および「cond_signal()」は、次のロックと組み合わせて使用​​できます。
リソースが使用可能になったことを協調スレッドに通知します。 それらは非常に似ています
「pthreads」にある関数を使用します。 ただし、ほとんどの場合、キューはより単純です。
使用し、より直感的です。 詳細については、threads::sharedを参照してください。

与え up コントロール
スレッドにCPUを明示的に放棄させると便利な場合があります
別のスレッド。 プロセッサを集中的に使用する何かをしている可能性があり、
ユーザーインターフェイススレッドは頻繁に呼び出されます。 とにかく、あなたが
スレッドがプロセッサを放棄することを望むかもしれません。

Perlのスレッドパッケージは、これを行う「yield()」関数を提供します。 「yield()」は
非常に簡単で、次のように機能します。

スレッドを使用します。

サブループ{
私の$thread=シフト;
私の$foo= 50;
while($ foo--){print("スレッド内$thread \ n"); }
スレッド->yield();
$ foo = 50;
while($ foo--){print("スレッド内$thread \ n"); }
}

私の$thr1=スレッド->create(\&loop、'first');
私の$thr2=スレッド->create(\&loop、'second');
私の$thr3=スレッド->create(\&loop、'サード');

「yield()」はCPUをあきらめるためのヒントにすぎないことを覚えておくことが重要です。
ハードウェア、OS、およびスレッドライブラリで実際に何が起こるか。 On 多くの オペレーティング
Nz - Nasyonal Mache dechanj nan peyi Zend limite 収率() is a ノーオペレーション。 したがって、構築すべきではないことに注意することが重要です
「yield()」呼び出しに関するスレッドのスケジューリング。 それはあなたのプラットフォームで動作するかもしれませんが
別のプラットフォームでは機能しません。

スレッド ユーティリティ ルーチン


Perlのスレッドパッケージの主力部分について説明しました。これらのツールを使用すると、
スレッド化されたコードとパッケージを作成するための準備が整っているはずです。 便利なものがいくつかあります
他のどこにも実際には収まらなかった小さな作品。

この試験は スレッド Am I に?
「threads->self()」クラスメソッドは、オブジェクトを取得する方法をプログラムに提供します
現在のスレッドを表します。このオブジェクトは、
スレッド作成から返されたもの。

スレッド IDが
「tid()」は、オブジェクトのスレッドのスレッドIDを返すスレッドオブジェクトメソッドです。
を表します。 スレッドIDは整数であり、プログラムのメインスレッドは0です。
現在、Perlは、プログラムでこれまでに作成されたすべてのスレッドに一意のTIDを割り当てます。
作成する最初のスレッドに1のTIDを割り当て、それぞれに対してTIDを1ずつ増やします。
作成された新しいスレッド。 クラスメソッドとして使用する場合、「threads-> tid()」は
独自のTIDを取得するスレッド。

です。 ボーマン スレッド この 同じ?
「equal()」メソッドはXNUMXつのスレッドオブジェクトを受け取り、オブジェクトが表す場合はtrueを返します。
同じスレッドで、そうでない場合はfalse。

スレッドオブジェクトにもオーバーロードされた「==」比較があるため、
通常のオブジェクトの場合と同じようにそれらを使用します。

この試験は スレッド です。 ランニング?
「threads->list()」は、現在のスレッドごとにXNUMXつずつ、スレッドオブジェクトのリストを返します。
実行中であり、切り離されていません。 最後の片付けなど、いろいろなことに便利です
プログラムの(もちろん、メインのPerlスレッドから):

#すべてのスレッドをループする
foreach my $ thr(threads-> list()){
$ thr-> join();
}

メインのPerlスレッドが終了したときに一部のスレッドの実行が終了していない場合、Perlは警告を発します
Perlが自分自身をクリーンアップすることは不可能なので、それについてあなたは死にます
スレッドが実行されています。

注:メインのPerlスレッド(スレッド0)は 切り離さ 状態なので、に表示されません
「threads->list()」によって返されるリスト。

A 完全


まだ混乱していますか? サンプルプログラムで、これまでに取り上げた内容のいくつかを紹介します。
このプログラムは、スレッドを使用して素数を見つけます。

1#!/ usr / bin / perl
2#prime-pthread、TomChristiansen提供
3
4厳密に使用します。
5警告を使用します。
6
7スレッドを使用します。
8 Thread::Queueを使用します。
9
10 サブ check_num {
11 my($ upload、$ cur_prime)= @_;
12私の$kid;
13 my $ downstream = Thread :: Queue-> new();
14 while(my $ num = $ upload-> dequeue()){
次の場合($ num%$ cur_prime);
16 if($ kid){
17 $ downstream-> enqueue($ num);
18} else {
19 print( "プライムが見つかりました:$ num \ n");
20 $ kid=スレッド->create(\&check_num、$ downstream、$ num);
21 if(!$ kid){
22 warn("申し訳ありません。スレッドが不足しています。\n");
最後に23;
24}
25}
26}
27 if($ kid){
28 $ downstream-> enqueue(undef);
29 $ kid-> join();
30}
31}
32
33 my $ stream = Thread :: Queue-> new(3..1000、undef);
34 check_num($ stream、2);

このプログラムは、パイプラインモデルを使用して素数を生成します。 の各スレッド
パイプラインには、チェックする数値、つまり素数を供給する入力キューがあります。
責任があり、失敗した番号をファネルする出力キュー
小切手。 スレッドにチェックに失敗した番号があり、子スレッドがない場合、
その場合、スレッドは新しい素数を検出している必要があります。 その場合、新しい子スレッドは
そのプライムのために作成され、パイプラインの終わりで立ち往生しています。

これはおそらく実際よりも少し混乱しているように聞こえるので、これを見てみましょう
少しずつプログラムして、それが何をするかを見てください。 (しようとしているかもしれないあなたのそれらのために
素数が何であるかを正確に覚えておいてください、それはによってのみ均等に割り切れる数です
それ自体と1.)

作業の大部分は、「check_num()」サブルーチンによって実行されます。このサブルーチンは、
その入力キューとそれが担当する素数。 入力を引いた後
キューとサブルーチンがチェックしているプラ​​イム(11行目)、新しいキューを作成します(行XNUMX)
13)後で作成する可能性のあるスレッド用にスカラーを予約します(12行目)。

14行目から26行目までのwhileループは、入力キューからスカラーを取得し、チェックします。
プライムに対して、このスレッドが責任を負います。 15行目は、
チェックする数を素数で割ったときの余り。 ある場合は、
数はプライムで均等に割り切れてはならないので、次のいずれかに渡す必要があります。
次のスレッドを作成した場合は(17行目)、作成していない場合は新しいスレッドを作成します。

新しいスレッドの作成は20行目です。これまでに作成したキューへの参照を渡します。
作成され、私たちが見つけた素数。 21行目から24行目では、次のことを確認します。
新しいスレッドが作成されたことを確認します。作成されていない場合は、
キュー。

最後に、ループが終了すると(キューに0または「undef」が含まれているため、
終了のメモとして機能します)、私たちは子供に通知を渡し、それが
子を作成した場合は終了します(27行目と30行目)。

一方、メインスレッドに戻ると、最初にキューを作成し(33行目)、すべてのキューをキューに入れます。
チェック用の3から1000までの数字と、終了通知。 その後、私たちがしなければならないすべて
ボールローリングを取得するには、キューと最初のプライムを「check_num()」サブルーチンに渡します。
(34線)。

それがどのように機能するかです。 とても簡単です。 多くのPerlプログラムと同様に、説明は次のとおりです。
プログラムよりはるかに長い。

異なる 実装 of スレッド


オペレーティングシステムの観点からのスレッド実装の背景。 がある
スレッドのXNUMXつの基本的なカテゴリ:ユーザーモードスレッド、カーネルスレッド、およびマルチプロセッサ
カーネルスレッド。

ユーザーモードスレッドは、プログラムとそのライブラリ内に完全に存在するスレッドです。 の
このモデルでは、OSはスレッドについて何も知りません。 それに関する限り、あなたのプロセスは
ただのプロセス。

これはスレッドを実装する最も簡単な方法であり、ほとんどのOSが起動する方法です。 ビッグ
欠点は、OSがスレッドについて何も知らないため、XNUMXつのスレッドがスレッドをブロックすると
すべてが行います。 一般的なブロッキングアクティビティには、ほとんどのシステムコール、ほとんどのI / O、および次のようなものが含まれます。
"睡眠()"。

カーネルスレッドは、スレッド進化の次のステップです。 OSはカーネルスレッドについて知っています、
そしてそれらを考慮に入れます。 カーネルスレッドとユーザーの主な違い-
モードスレッドがブロックされています。 カーネルスレッドでは、単一のスレッドをブロックするものはブロックしません
他のスレッドをブロックします。 これは、カーネルがブロックするユーザーモードスレッドには当てはまりません。
スレッドレベルではなく、プロセスレベルで。

これは大きな前進であり、スレッド化されたプログラムのパフォーマンスを大幅に向上させることができます
非スレッドプログラム。 たとえば、I/Oの実行をブロックするスレッドはブロックしません
他のことをしているスレッド。 各プロセスには、まだXNUMXつのスレッドしか実行されていません。
ただし、システムに搭載されているCPUの数に関係なく、XNUMX回だけです。

カーネルスレッドはいつでもスレッドを中断できるため、カーネルスレッドはいくつかのスレッドを明らかにします
プログラムで行う可能性のある暗黙のロックの仮定。 たとえば、
「$x= $ x + 2」のように単純な場合、$ xが表示されている場合、カーネルスレッドで予期しない動作をする可能性があります。
他のスレッド、別のスレッドがフェッチされた時間の間に$xを変更した可能性があるため
右側と新しい値が保存される時刻。

マルチプロセッサカーネルスレッドは、スレッドサポートの最後のステップです。 マルチプロセッサを使用
複数のCPUを搭載したマシン上のカーネルスレッドの場合、OSはXNUMXつ以上のスレッドを
異なるCPUで同時に実行します。

これにより、スレッド化されたプログラムのパフォーマンスが大幅に向上する可能性があります。
スレッドは同時に実行されます。 ただし、トレードオフとして、これらのしつこいものはどれも
基本的なカーネルスレッドでは表示されなかった可能性のある同期の問題が表示されます
すさまじい勢いで。

スレッドへのさまざまなレベルのOSの関与に加えて、さまざまなOS(および
特定のOSの異なるスレッド実装)CPUサイクルを
違う方法。

協調マルチタスクシステムでは、XNUMXつのことのいずれかが発生した場合、実行中のスレッドが制御を放棄します
起こる。 スレッドがyield関数を呼び出すと、制御を放棄します。 それもあきらめます
I / Oの実行など、スレッドがブロックする原因となる何かをスレッドが実行するかどうかを制御します。
協調マルチタスクの実装では、XNUMXつのスレッドが他のすべてのスレッドをCPUに飢えさせる可能性があります
それがそう選択した場合の時間。

プリエンプティブマルチタスクシステムは、システムが定期的にスレッドを中断します
次に実行するスレッドを決定します。 プリエンプティブマルチタスクシステムでは、XNUMXつのスレッド
通常、CPUを独占することはありません。

一部のシステムでは、協調スレッドとプリエンプティブスレッドが同時に実行される場合があります。
(リアルタイムの優先順位で実行されているスレッドは、たとえば、
通常の優先順位で実行されているスレッドはプリエンプティブに動作します。)

最近のほとんどのオペレーティングシステムは、プリエンプティブマルチタスクをサポートしています。

性能 検討事項


Perlを比較するときに覚えておくべき主なこと iスレッド 他のスレッディングモデルには
新しいスレッドが作成されるたびに、すべての変数とデータの完全なコピーが作成されるという事実
親スレッドのを取得する必要があります。 したがって、スレッドの作成にはかなりの費用がかかる可能性があります。
メモリ使用量と作成に費やされた時間の観点から。 これらのコストを削減する理想的な方法
比較的短い数の長寿命スレッドを使用することです。これらはすべて、かなり早い段階で作成されます。
(ベーススレッドが大量のデータを蓄積する前)。 もちろん、これは必ずしもそうではないかもしれません
可能であるため、妥協する必要があります。 ただし、スレッドが作成された後、
パフォーマンスと余分なメモリ使用量は、通常のコードと少し異なるはずです。

また、現在の実装では、共有変数はもう少し多くのメモリを使用することに注意してください
通常の変数よりも少し遅いです。

プロセススコープ 変更


スレッド自体は別個の実行スレッドであり、Perlデータはスレッドであることに注意してください-
明示的に共有されていない限り、スレッドはプロセススコープの状態に影響を与え、影響を与える可能性があります
すべてのスレッド。

この最も一般的な例は、「chdir()」を使用して現在の作業ディレクトリを変更することです。
XNUMXつのスレッドが「chdir()」を呼び出し、すべてのスレッドの作業ディレクトリが変更されます。

プロセススコープの変更のさらに抜本的な例は、「chroot()」です。
すべてのスレッドが変更され、(「chdir()」とは対照的に)どのスレッドもそれを元に戻すことはできません。

プロセススコープの変更のその他の例には、「umask()」やuidとgidの変更が含まれます。

「fork()」とスレッドの混合を考えていますか? 横になって気持ちが出るまでお待ちください
パスします。 「fork()」のセマンティクスはプラットフォームによって異なることに注意してください。 例えば、
一部のUnixシステムは、現在のすべてのスレッドを子プロセスにコピーしますが、他のシステムは
「fork()」を呼び出したスレッドをコピーします。 あなたは警告されました!

同様に、信号とスレッドの混合には問題がある場合があります。 実装はプラットフォームです-
依存しており、POSIXセマンティクスでさえあなたが期待するものではないかもしれません(そしてPerlは
完全なPOSIXAPIを提供します)。 たとえば、信号を保証する方法はありません
マルチスレッドのPerlアプリケーションに送信されると、特定のスレッドによってインターセプトされます。
(ただし、最近追加された機能は、間で信号を送信する機能を提供します
スレッド。 詳細については、スレッドの「スレッド署名」を参照してください。)

スレッドセーフ of エントルピー ライブラリ


さまざまなライブラリ呼び出しがスレッドセーフであるかどうかは、Perlの制御の範囲外です。 頻繁に電話する
スレッドセーフではないという問題には、「localtime()」、「gmtime()」、関数が含まれます。
ユーザー、グループ、ネットワーク情報( "getgrent()"、 "gethostent()"など)を取得します。
「getnetent()」など)、「readdir()」、「rand()」、「srand()」。 一般的に、それを呼び出します
いくつかのグローバルな外部状態に依存します。

Perlがコンパイルされているシステムにそのような呼び出しのスレッドセーフなバリアントがある場合、それらは
使用済み。 それを超えて、Perlは呼び出しのスレッドセーフまたは-アンセーフティに翻弄されます。
Cライブラリの呼び出しドキュメントを参照してください。

一部のプラットフォームでは、結果バッファが多すぎると、スレッドセーフなライブラリインターフェイスが失敗する場合があります
小さい(たとえば、ユーザーグループデータベースはかなり大きい場合があり、再入可能
インターフェイスは、これらのデータベースの完全なスナップショットを持ち歩く必要がある場合があります)。 Perlが起動します
小さなバッファを使用しますが、結果が出るまで結果バッファを再試行して拡張し続けます
フィットします。 この無限の成長がセキュリティやメモリ消費の理由で悪いと思われる場合は、
最大バイト数に定義された「PERL_REENTRANT_MAXSIZE」でPerlを再コンパイルできます
許可します。

まとめ


完全なスレッドチュートリアルは本を埋めることができます(そして、何度もあります)が、私たちが持っているもので
このイントロダクションでカバーされているので、スレッド化されたPerlになるための道は順調に進んでいるはずです。
専門家。

onworks.netサービスを使用してオンラインでperlthrtutを使用する


無料のサーバーとワークステーション

Windows と Linux のアプリをダウンロード

Linuxコマンド

Ad