20201115のNode.jsに関する記事は4件です。

多言語からみるマルチコアの活かし方

多言語からみるマルチコアの活かし方

はじめに

近年では1つのCPUに複数のコアが搭載されたマルチコアが一般的になっています。
しかし、現状のプログラミング言語ではエンジニアが意識せずにマルチコアをしたプログラムを作ることは難しいです。
そこで、様々な言語から見たマルチコアの活かし方について説明していきます。

プロセスとスレッド

プロセスとは1つ1つのアプリケーションといった実行中のプログラムのことで、スレッドは CPU利用の単位です。プロセスは次のように1つ以上のスレッドを持っており、CPUのコア数分だけスレッドを処理することができます。(また、近年ではSMTという技術によって1つの物理コアで2スレッドといった複数のスレッドを処理することができます。2コア4スレッドみたいなやつです)
スレッドとプロセス.png
スレッドとプロセスとCPU.png
マルチコアを有効活用してプログラムを実行するためにはCPUが処理できるコア数に対して適切な数のスレッドをプログラム側で生成する必要があります。コア数以上のスレッドを生成する事も可能ですが、CPUはコア数分のスレッドしか処理を行うことができず、実行するスレッドの切り替えにより処理が遅くなってしまう問題が発生します。

並列と並行

並列(parallel)と並行(concurrent)という似たような言葉が存在しますが、違うものを示します。
並列(parallel)とは複数の処理を同時に行うことで、複数のコアで複数のスレッドを処理するような場合を示します。(シングルコアでは複数処理を同時に実行できないため並列を実現することはできません。)
並列.png
並行(concurrent)とは複数の処理を切り替えながら同時に実行しているようにすることで、1つのスレッドで複数の処理を切り替えながら実行するような場合を示します。
並行.png
複数のスレッドで処理を切り替えながら実行することも可能なため、並列かつ並行を実現することも可能です。

C10K問題

WebサーバーのApacheではユーザのリクエスト毎にプロセスを生成する方式を取っており、クライアントが約1万台に達すると、Webサーバーのハードウェア性能に余裕があるにも関わらず、レスポンス性能が大きく下がるというC10K問題というものが存在していました。(C10K問題の具体的な原因はこちらの記事が分かりやすかったです。)
そこで、nginxやNode.jsではシングルスレッドで非同期I/Oに処理をすることにより、並行に処理を行を行うことでC10K問題を解決しようとしました。

Node.js

前述したように、Node.jsはシングルスレッドで動作をし、async/awaitといった非同期処理で並行に処理を行うというアプローチがされています。イメージとしては下図のように、外部APIアクセスをする際に結果が返ってくるまでの時間に他の処理を行い、結果が取得できたらその処理の続きを行うようなイメージです。(詳細はこちらの記事が分かりやすかったです。)
非同期.png
従って標準の非同期処理を行う場合は、マルチコアの性能を引き出すことができません。そこでNode.jsではClusterを用いてプロセスを複数作成するか、worker_threadsを用いてスレッドを複数作成する必要があります。このようにマルチコアコアを活かすためには、プログラム側からプロセスかスレッドを複数作成してあげる必要があり、マルチスレッドでは変数の値の共有を行うことができますが、マルチプロセスではメモリ空間が分離され、変数の値の共有ができないというそれぞれのメリット・デメリットが存在します。

RubyやPythonで起こるGIL

Node.jsでは、プロセスかスレッドを複数作成してあげることでマルチコアを活かすことができました。しかし、RubyやPythonではグローバルインタプリタロック(GIL)というものが存在し、複数のスレッドを作成しても並列に実行することができません。(正確にはC言語で実装されたCPythonとCRubyの場合ですが、ここでは省略します。)
従って、これらの言語でマルチコアを活かそうとした場合、マルチスレッドでは実現できず、プロセスを複数作成してあげる必要があります。

Go言語でのgoroutine

Go言語では、goroutineというものを用いて非同期処理を並列・並行に実現しており、デフォルトでCPUのコア数が設定されたGOMAXPROCSというものが設定されています。この値の数だけスレッドが用意され、そのスレッドの中で軽量スレッドであるgoroutineを実行します。
CPUのコア数が4のGOMAXPROCS=4の場合のイメージとしては下図です。
goroutine.png
このようにgoroutineを用いることによりマルチコアを活かして並列・並行にプログラムの実行を行えます。
(goroutineが軽量な理由についてはこちらの記事が分かりやすかったです。)

Rustでのasync/await

Rustでは、async/awaitを用いて非同期処理を行えます。この際、どのランタイムを用いるかによって非同期処理のスレッドの実行割当方法を選択できます。
人気があるランタイムとしては、tokioが挙げられます。tokioではコア数に対してスレッドが生成され、そのスレッドに非同期処理が渡されるというgoroutineと同様のマルチコアの活かし方が実現できます。(他の割当方法や、Rustでの非同期処理についてはこちらの記事が分かりやすかったです。特にここの実行モデルについてが分かりやすいです。)

さいごに

RubyやPythonでは仕組み上マルチスレッドにすることが難しく、Node.jsの非同期処理は、そのままではマルチコアを活かすことができませんでした。
しかし、近年人気のあるGo言語やRustでは非同期処理を呼ぶ事により、エンジニアが意識せずに並列・並行処理を行え、マルチコアを活かすことができます。
マルチコアのCPUが一般的になってきた現代に、Go言語やRustが人気がある事も納得です。

参考

【図解】CPUのコアとスレッドとプロセスの違い,コンテキストスイッチ,マルチスレッディングについて
プロセスとスレッドとタスクの違いを知ってUnity非同期完全理解に近付く
Node.jsの非同期I/Oについて調べてみた
いまさら聞けないNode.js
Pythonで並列処理をするなら知っておくべきGILをできる限り詳しく調べてみた
goroutineはなぜ軽量なのか
Rustの非同期プログラミングをマスターする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

変数のスコープを間違えてたメモ

やりたかったこと

LINEで飲みに行きたいと言ったらメッセージが返ってくる予定

あかんかった

単純なミスだからインデントをちゃんとつけよう。(どこのカッコの中に入ってるかわかりにくかった)
飲みに行きたい1人目って返信してもらうはずが、なにも来なかった。
定義されたスコープが違うからと認識しているが、別の理由か?(use strictをグローバルレベルのところには書いてた。)
もう少し調べよう。

        // ユーザーメッセージが飲みに行きたいものかどうか
        if (check_go(event.message.text)) {
            let pushText = '';
            console.log("飲みに行きたいってさ");

            pushText = "飲みに行きたい人1人目";
        }
    // 中略
        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText,
        });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Create-react-appのindex.htmlで条件分岐してmetaタグなどを入れる

Create-react-appで環境毎にhtmlタグを変えたい時のメモ

本番環境と開発環境でmetaタグを変えたかった(開発環境にnoindexを入れたかった)ので、調べてみたらejsのテンプレートに対応してるみたいです。

参考:Conditional content in index.html

ejsの文法

参考:ejs.co

とりあえず<% %>で囲った中はjs書いていいみたいです。変数をそのまま出力するときは<%=何でしょうか。今後使う時が来たら調べてみます。

index.html
<% if (user) { %>
  <h2><%= user.name %></h2>
<% } %>

結論

自分は下記metaタグ入れたかっただけなので、結論こう書きました。環境変数とかもよく分かってないのでもっとちゃんとしたやり方あるかもしれないです。

ビルド時に本番環境サイトはREACT_APP_ENVにprod、開発環境サイトはREACT_APP_ENVにdevと入れています。

<meta name="robots" content="noindex" />
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <!-- headに入れてるタグ色々 -->
    <% if(process.env.REACT_APP_ENV === "dev") { %>
    <meta name="robots" content="noindex" />
    <% } %>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BookShelfアノテーションで複数取得する方法で迷ったのでアウトプットする。

はじめに

BookShelfを使ったデータの取り込みで、データベースの依存関係が複数ある場合の
クエリ取得方法で迷ったので、やった内容をアウトプットする。
その為、説明等は適当な部分多い。

作成していたアプリは、ルーム毎にユーザとメッセージを保存する。
メッセージ交換用アプリ

Bookshelfとは

Node.jsとかで使えるデータベースに対して、データを取得できる便利な道具です。

アノテーションについて

DBのテーブル同士を関連づけて取得できるものになります。

 var Room = Bookshelf.Model.extend({
   tableName: 'rooms',
   hasTimestamps: true,
   messages: function() {
     return this.hasMany(Message);
  }
 });

アノテーションに当たる部分

  • hasMany : 一対多(Roomテーブルの1行に紐づくMessageテーブルの行は多数あるって意味)
  • belongsTo : 一対一(MessageとUserテーブルで紐づくデータは1行に対して1行のみ)
//Roomテーブルを取得する際に紐ずいているMessageテーブルのデータを取得する。

   messages: function() {
     return this.hasMany(Message);
  }

DBの構成

  • こんなかんじ Image from Gyazo

階層アノテーション取得方法

  • それぞれのテーブル定義
var Bookshelf = require('bookshelf')(knex);

var User = Bookshelf.Model.extend({
  tableName: 'users'
});

var Room = Bookshelf.Model.extend({
  tableName: 'rooms',
  hasTimestamps: true,
  messages: function() {
    return this.hasMany(Message);
  }
});

var Message = Bookshelf.Model.extend({
  tableName: 'messages',
  hasTimestamps: true,
  user: function() {
    return this.belongsTo(User);
  }
});

結局のところ、一対多のデータから、さらに一対一のデータを取り出すには、fetchでwithRelatedで
'message.user'のように、Roomテーブルでアソシエーションとして指定したmessageからさらにMessageでアソシエーションした
userを指定するとデータとして取得できる。

/**
*     RoomテーブルからroomIdを紐づけてMessageテーブルのデータを取得
*                    Messageテーブルからい1対1関係のデータを取得する。
**/

                  new Room().query({where:{id : roomId}})
                          .fetch({withRelated:['messages','messages.user']})
                          .then((messageRoom) => {

                            res.render('chat',{
                              roomName: messageRoom.attributes.room_name,
                              roomId : messageRoom.attributes.id,
                              userName: login.name,
                              userId: login.id,
                              message: messageRoom.relations.messages.models
                            });


                            var messageTests = messageRoom.relations.messages.models;

                            for(var val of messageTests){

                              console.log(val.attributes.message);
                            }

                          });

また、一対多の時にデータを取得する場合は、
room.relations.messages.modelsというようにいったんmodelsを指定してから
attributes.[カラム名]といった形でデータを取得する。
一対一の場合には、messages.relations.user.attributes.[カラム名]という形にすればいい。

初めてQiita書いたので、間違っている事もあると思われますが、ご了承ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む