Cプログラミング
 Computer >> コンピューター >  >> プログラミング >> Cプログラミング

暗黙のスレッドと言語ベースのスレッド


暗黙のスレッド

困難に対処し、マルチスレッドアプリケーションの設計をより適切にサポートする1​​つの方法は、スレッドの作成と管理をアプリケーション開発者からコンパイラとランタイムライブラリに移すことです。これは暗黙のスレッドと呼ばれ、今日人気のあるトレンドです。

暗黙のスレッド 主に、スレッドの管理を隠すためのライブラリまたは他の言語サポートの使用です。最も一般的な暗黙のスレッドライブラリは、CのコンテキストでのOpenMPです。

OpenMP は、コンパイラ指令のセットであり、C、C ++、またはFORTRANで記述されたプログラム用のAPIであり、共有メモリ環境での並列プログラミングのサポートを提供します。 OpenMPは、並列領域を、並列で実行される可能性のあるコードのブロックとして識別します。アプリケーション開発者は、コンパイラディレクティブを並列領域のコードに挿入します。これらのディレクティブは、OpenMPランタイムライブラリに領域を並列で実行するように指示します。次のCプログラムは、printf()ステートメントを含む並列領域の上のコンパイラ指令を示しています。

#include <omp.h>
#include <stdio.h>
int main(int argc, char *argv[]){
   /* sequential code */
   #pragma omp parallel{
      printf("I am a parallel region.");
   }
   /* sequential code */
   return 0;
}

出力

I am a parallel region.

OpenMPがディレクティブに遭遇したとき

#pragma omp parallel

システム内のコアを処理しているスレッドをできるだけ多く作成します。したがって、デュアルコアシステムの場合は2つのスレッドが作成され、クアッドコアシステムの場合は4つのスレッドが作成されます。などなど。次に、すべてのスレッドが同時に並列領域を実行します。各スレッドが並列領域を出ると、終了します。 OpenMPは、ループの並列化など、コード領域を並列で実行するためのいくつかの追加のディレクティブを提供します。

OpenMPを使用すると、並列化のディレクティブを提供するだけでなく、開発者はいくつかのレベルの並列処理から選択できます。たとえば、スレッド数を手動で設定できます。また、開発者は、データがスレッド間で共有されているのか、スレッド専用であるのかを識別できます。 OpenMPは、Linux、Windows、およびMacOSXシステム用のいくつかのオープンソースおよび商用コンパイラで利用できます。

グランドセントラルディスパッチ(GCD)

Grand Central Dispatch(GCD)(AppleのMac OS XおよびiOSオペレーティングシステム用のテクノロジ)は、C言語の拡張機能、API、およびアプリケーション開発者が実行するコードのセクションを見つけることができるランタイムライブラリの組み合わせです。平行。 OpenMPと同様に、GCDもスレッド化の詳細のほとんどを管理します。ブロックと呼ばれるCおよびC++言語の拡張機能を識別します。ブロックは、単に自己完結型の作業単位です。これは、中括弧{}のペアの前に挿入されたカレットˆによって指定されます。ブロックの簡単な例を以下に示します-

{
   ˆprintf("This is a block");
}

ブロックをディスパッチキューに配置することにより、実行時の実行用にブロックをスケジュールします。 GCDがキューからブロックを削除すると、GCDは、管理しているスレッドプールから使用可能なスレッドにブロックを割り当てます。シリアルとコンカレントの2種類のディスパッチキューを識別します。シリアルキューに配置されたブロックは、FIFO順に削除されます。ブロックがキューから削除されると、別のブロックが削除される前に実行を完了する必要があります。各プロセスには、独自のシリアルキュー(メインキューと呼ばれます)があります。開発者は、特定のプロセスに対してローカルな追加のシリアルキューを作成できます。シリアルキューは、いくつかのタスクを順番に実行するのに役立ちます。並行キューに配置されたブロックもFIFO順に削除されますが、一度に複数のブロックを削除できるため、複数のブロックを並行して実行できます。システム全体の同時ディスパッチキューは3つあり、優先度に従って、低、デフォルト、高の3つに区別されます。優先度は、ブロックの相対的な重要性の見積もりを表します。簡単に言うと、優先度の高いブロックは、優先度の高いディスパッチキューに配置する必要があります。次のコードセグメントは、デフォルト優先度の同時キューを取得し、dispatch async()関数を使用してブロックをキューに送信する方法を示しています。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch async(queue, ˆ{ printf("This is a block."); });

内部的には、GCDのスレッドプールはPOSIXスレッドで構成されています。 GCDはプールを積極的に管理し、アプリケーションの需要とシステム容量に応じてスレッドの数を増減できるようにします。

オブジェクトとしてのスレッド

代替言語では、古代のオブジェクト指向言語は、オブジェクトとしてのスレッドを使用した明示的なマルチスレッドサポートを提供します。これらの形式の言語では、スレッドクラスを拡張するか、対応するインターフェイスを実装するように記述されたクラス領域。このスタイルは、コードが明示的なスレッド管理で記述されているため、Pthreadアプローチに似ています。ただし、クラス内の情報のカプセル化と追加の同期オプションにより、タスクが変更されます。

Javaスレッド

Javaは、使用可能なスレッドカテゴリと実行可能インターフェイスを提供します。それぞれが、スレッドのエントリ目的を定義するpublic void run()テクニックを実装する必要があります。オブジェクトのインスタンスが割り当てられると、そのインスタンスでstart()テクニックを呼び出すことによってスレッドが開始されます。 Pthreadと同様に、スレッドの開始は非同期であり、実行の時間的配置は非決定的です。

Pythonスレッド

Pythonはさらに、マルチスレッド用の2つのメカニズムを提供します。 1つのアプローチは、関数名がライブラリメソッドthread.start_new_thread()に渡される場合は常に、Pthreadスタイルに相当します。このアプローチは非常に多く、スレッドの開始後にスレッドを結合または終了する柔軟性がありません。追加の柔軟な手法は、スレッドモジュールを使用して、スレッドを拡張するクラスのアウトラインを作成することです。スレッド。 Javaのアプローチとほぼ同じように、カテゴリにはスレッドのエントリ目的を与えるrun()メソッドが必要です。このカテゴリからオブジェクトがインスタンス化されると、後で明示的に開始して結合することができます。

言語設計としての並行性

新しいプログラミング言語は、同時実行の仮定を言語スタイル自体に直接組み込むことにより、競合状態を回避しています。例として、Goは、些細な暗黙のスレッド手法(ゴルーチン)をチャネルと組み合わせます。これは、明確に定義されたメッセージパッシング通信のスタイルです。 Rustは、Pthreadと同じ明確なスレッドアプローチを採用しています。ただし、Rustには非常に強力なメモリ保護があり、ソフトウェアエンジニアによる追加の作業は必要ありません。

Goroutines

Go言語には、暗黙的なスレッド化のための簡単なメカニズムが含まれています。呼び出しの前にキーワードgoを配置します。新しいスレッドには、メッセージパッシングチャネルへの関連付けが渡されます。次に、ほとんどのスレッドがsuccess:=<-messagesを呼び出し、チャネルで干渉スキャンを実行します。ユーザーが7の正しい推測を入力すると、キーボード監査スレッドがチャネルに書き込み、ほとんどのスレッドが進行できるようにします。

チャネルとゴルーチンはGo言語のコアコンポーネントであり、ほとんどすべてのプログラムがマルチスレッド化されるという信念の下で設計されました。このスタイルの代替手段は、イベントモデルを合理化し、言語自体がスレッドの管理とプログラミングの責任を最新のものにすることを可能にします。

Rustの同時実行性

もう1つの言語は、並行性を中心的な設計機能として、近年作成されたRustです。次の例は、thread ::spawn()を使用して新しいスレッドを作成する方法を示しています。このスレッドは、後でjoin()を呼び出すことで結合できます。 ||で始まるthread::spawn()への引数クロージャーと呼ばれ、無名関数と考えることができます。つまり、ここの子スレッドはaの値を出力します。

use std::thread;
fn main() {
   /* Initialize a mutable variable a to 7 */
   let mut a = 7;
   /* Spawn a new thread */
   let child_thread = thread::spawn(move || {
      /* Make the thread sleep for one second, then print a */
      a -= 1;
      println!("a = {}", a)
   });
   /* Change a in the main thread and print it */
   a += 1;
   println!("a = {}", a);
   /* Join the thread and print a again */
   child_thread.join();
}

ただし、このコードには、Rustの設計の中心となる微妙な点があります。新しいスレッド内(クロージャー内のコードを実行)では、変数はこのコードの他の部分のaとは異なります。複数のスレッドが同じメモリにアクセスするのを防ぐ、非常に厳密なメモリモデル(「所有権」と呼ばれる)を適用します。この例では、moveキーワードは、生成されたスレッドが独自に使用するためにaの個別のコピーを受け取ることを示しています。 2つのスレッドのスケジューリングに関係なく、メインスレッドと子スレッドは別個のコピーであるため、互いのaの変更に干渉することはできません。 2つのスレッドが同じメモリへのアクセスを共有することはできません。


  1. 解決済み:印刷キューWindows10および11を削除できない

    プリンタが機能しないというエラーはユーザーの間でよく見られますが、ドキュメントがWindows10またはWindows11の印刷キューから削除されないという問題もあります。 コンテンツ: 印刷キューの概要を削除できません: 印刷ジョブがキューに詰まるのはなぜですか? Windows 10を修正する4つの方法で印刷キューがクリアされませんか? 印刷キューの概要を削除できません: Windows10またはWindows11では、いつでもスタックした印刷ジョブが発生する可能性があります。最も一般的なものは次のとおりです。 1.ドキュメントを印刷した後も、印刷キューに表示され、W

  2. C言語での暗黙的および明示的な型変換とは何ですか?

    あるデータ型を別のデータ型に変換することを型変換と呼びます。 暗黙の型変換 明示的な型変換 暗黙の型変換 オペランドのデータ型が異なる場合、コンパイラは暗黙的な型変換を提供します。 これは、小さなデータ型を大きなデータ型に変換することにより、コンパイラによって自動的に実行されます。 int i,x; float f; double d; long int l; ここで、上記の式は最終的に「double」値に評価されます。 例 以下は、暗黙的な型変換の例です- int x; for(x=97; x<=122; x++){    printf