Nginx メモリプールのソースコード分析

Nginx メモリプールのソースコード分析

メモリプールの概要

メモリ プールは、メモリが実際に使用される前にバックアップとして事前に割り当てられる、通常は同じサイズの一定数のメモリ ブロックです。新しいメモリ需要が発生すると、メモリ ブロックの一部がメモリ プールから割り当てられます。メモリ ブロックが十分でない場合は、新しいメモリが要求されます。

メモリ プールの利点には、システムへのメモリの適用と解放にかかる時間のオーバーヘッドの削減、頻繁なメモリ割り当てによって発生するメモリの断片化の問題の解決、プログラムのパフォーマンスの向上、コード作成時のプログラマーのメモリへの注意の軽減などがあります。

現在、一般的なメモリ プールの実装としては、STL のメモリ割り当て領域、boost のobject_pool 、nginx のngx_pool_t 、Google のオープン ソース プロジェクト TCMalloc などがあります。

Nginx は、利便性のために、ngx_str_t、ngx_array_t、ngx_pool_t などの多くの便利なデータ構造をカプセル化しています。メモリ プールに関しては、nginx の設計は非常に洗練されており、学習する価値があります。この記事では、nginx メモリ プールのソース コードの紹介に焦点を当て、実際のコード例を使用してさらに説明します。

1. nginxデータ構造

// SGI STL の小メモリブロックと大メモリブロックの分割点: 128B
// nginx (HTTP サーバーのすべてのモジュールにメモリを割り当てます) メモリの小さなブロックと大きなブロックの分割点: 4096B
# NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) を定義します 

// メモリプールのデフォルトサイズ #define NGX_DEFAULT_POOL_SIZE (16 * 1024)

// メモリプールのバイトアラインメント、SGI STL は 8B
#定義 NGX_POOL_ALIGNMENT 16
# NGX_MIN_POOL_SIZE を ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), で定義します。\
		                         NGX_プール_アラインメント

// 割り当てられたメモリを 16 の整数倍に調整します #define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
typedef 構造体 ngx_pool_s ngx_pool_t;

typedef構造体{
    u_char *last; // 使用可能なメモリの開始アドレスを指します u_char *end; // 使用可能なメモリの終了アドレスを指します ngx_pool_t *next; // 次のメモリ ブロックを指します ngx_uint_t failed; // 現在のメモリ ブロックがスペースの割り当てに失敗した回数} ngx_pool_data_t;

// メモリプールブロック型 struct ngx_pool_s {
    ngx_pool_data_t d; // メモリプールブロックヘッダー情報 size_t max;	
    ngx_pool_t *current; // スペースを割り当てるために使用できるメモリ ブロックの開始アドレスを指します (失敗 < 4) ngx_chain_t *chain; // すべてのメモリ プール ブロックを接続します ngx_pool_large_t *large; // 大きなメモリ ブロックのエントリ ポインター ngx_pool_cleanup_t *cleanup; // メモリ プール ブロックのクリーンアップ操作。ユーザーは、メモリ プール ブロックを解放する前にクリーンアップ操作を実行するコールバック関数を設定できます ngx_log_t *log; // ログ };

ここに画像の説明を挿入

2. nginxはOSからスペースngx_create_poolを申請します

// サイズに応じてメモリを開放する ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){
    ngx_pool_t *p;
	// システム プラットフォームによって定義されたマクロとユーザーが実行したサイズに応じて、さまざまなプラットフォームの API を呼び出してメモリ プールを開きます。p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    (p == NULL)の場合{
        NULL を返します。
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t); // 使用可能なメモリの開始アドレスを指します p->d.end = (u_char *) p + size; // 使用可能なメモリの終了アドレスを指します p->d.next = NULL; // 次のメモリ ブロックを指します。メモリ ブロックは要求されたばかりなので、空白のままです p->d.failed = 0; // メモリ ブロックが正常に割り当てられたかどうか size = size - sizeof(ngx_pool_t); // 使用可能なスペース = 合計スペース - ヘッダー情報 // 指定されたサイズが 1 ページより大きい場合は 1 ページを使用し、それ以外の場合は指定されたサイズを使用します // max = min(size, 4096)、max は開始情報を除いたメモリ ブロックのサイズを参照します p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p; // スペースを割り当てるために使用できるメモリ ブロックの開始アドレスを指します p->chain = NULL;
    p->large = NULL; // 小さなメモリ ブロックはメモリ ブロックに直接割り当てられ、大きなメモリ ブロックは large が指すメモリに割り当てられます。p->cleanup = NULL;
    p->log = ログ;

    p を返します。
}

ここに画像の説明を挿入

3. nginxはメモリプールからスペースを申請します

空所 *
ngx_palloc(ngx_pool_t *プール、size_t サイズ)
{
#if !(NGX_DEBUG_PALLOC)
    (サイズ <= プール-> 最大) の場合 {
    	// 現在割り当てられているスペースは最大値より小さいため、メモリ割り当てが小さいです。 return ngx_palloc_small(pool, size, 1); // メモリの配置を考慮します}
#終了

    ngx_palloc_large(プール、サイズ) を返します。
}

空所 *
ngx_pnalloc(ngx_pool_t *プール、size_t サイズ)
{
#if !(NGX_DEBUG_PALLOC)
    (サイズ <= プール-> 最大) の場合 {
        return ngx_palloc_small(pool, size, 0); // メモリのアラインメントは考慮されません}
#終了

    ngx_palloc_large(プール、サイズ) を返します。
}

void* ngx_pcalloc(ngx_pool_t *プール、size_t サイズ){
    void *p;
    p = ngx_palloc(pool, size); // メモリアライメントを考慮する if (p) {
        ngx_memzero(p, size); // メモリを0に初期化する
    }

    p を返します。
}

ngx_palloc_smallは割り当て効率が高く、ポインタオフセットのみを作成します。

静的 ngx_inline void *
ngx_palloc_small(ngx_pool_t *プール、size_t サイズ、ngx_uint_t 配置)
{
    u_char *m;
    ngx_pool_t *p;
	// 最初のメモリ ブロックの現在のポインタが指すメモリ プールから割り当てます。p = pool->current;

    する {
        m = p->d.last; // mは割り当て可能なメモリの開始アドレスを指します if (align) {
        	// m を NGX_ALIGNMENT の整数倍に調整します。m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
		//メモリプールからメモリを割り当てるためのコアコードif ((size_t) (p->d.end - m) >= size) {
        	// 割り当て可能なスペース >= 要求されたスペースの場合 // d.last ポインターをオフセットし、空きスペースの最初のアドレスを記録します p->d.last = m + size;
            m を返します。
        }
        // 現在のメモリブロックの空き領域は割り当てるには不十分です。次のメモリブロックがある場合は、次のメモリブロックに進みます。 // ない場合は、p は NULL に設定され、終了すると同時に
        p = p->d.next;
    } ながら (p);
	
    ngx_palloc_block(プール、サイズ) を返します。
}

現在のメモリ プールには割り当てるのに十分なブロックがあります:

ここに画像の説明を挿入

現在のメモリ プールには割り当てるのに十分なブロックがありません:

  1. 新しいメモリ ブロックを開き、新しいメモリ ブロック ヘッダー情報の last、end、next、failed フィールドを変更します。
  2. 以前のメモリ ブロックはすべて失敗しました++
  3. 新しいメモリブロックを以前のメモリブロックに接続します
静的void * ngx_palloc_block(ngx_pool_t *プール、size_tサイズ){
    u_char *m;
    size_t pサイズ;
    ngx_pool_t *p、*新しい;
	// 前のメモリ ブロックと同じサイズのメモリ ブロックを開きます。psize = (size_t) (pool->d.end - (u_char *) pool);
	
	// psize を NGX_POOL_ALIGNMENT の整数倍に揃えた後、OS からスペースを申請します m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    (m == NULL)の場合{
        NULL を返します。
    }

    new = (ngx_pool_t *) m; // 新しく割り当てられたメモリ ブロックの開始アドレスを指します new->d.end = m + psize; // 新しく割り当てられたメモリ ブロックの終了アドレスを指します new->d.next = NULL; // 次のメモリ ブロックのアドレスは NULL です 
    new->d.failed = 0; // 現在のメモリ ブロックがスペースの割り当てに失敗した回数 // ヘッダー情報の末尾を指しますが、max、current、chain などは最初のメモリ ブロックにのみ存在します m += sizeof(ngx_pool_data_t);  
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size; // last は現在のブロックの空き領域の開始アドレスを指します	
	// 毎回 pool->current からスペースが割り当てられるため // 実行がここに到達すると、新しいメモリ ブロックを除いて、他のすべてのメモリ ブロックの割り当てに失敗します (p = pool->current; p->d.next != NULL; p = p->d.next) {
    	// すべてのメモリ ブロックに対して、メモリ ブロックの割り当て失敗数が 4 を超えるまで failed++ が追加されます。 // これは、メモリ ブロックの残りのスペースが非常に少なく、これ以上スペースを割り当てることができないことを意味します。 // 次に、現在のポインタを変更し、次回は現在のポインタからスペースの割り当てを開始します。再度割り当てるときに、以前のメモリ ブロックをトラバースする必要はありません。 if (p->d.failed++ > 4) {
            プール->current = p->d.next;
        }
    }
	
    p->d.next = new; // 割り当て可能なスペースの最初のメモリ ブロックと新しく開いたメモリ ブロックを接続します。 return m;
}

ここに画像の説明を挿入

4. 大きなメモリブロックの割り当てと解放

typedef 構造体 ngx_pool_large_s ngx_pool_large_t;

構造体 ngx_pool_large_s {
    ngx_pool_large_t *next; // 次の大きなメモリ ブロックの開始アドレス void *alloc; // 大きなメモリ ブロックの開始アドレス };

静的void * ngx_palloc_large(ngx_pool_t *プール、size_tサイズ){
    void *p;
    ngx_uint_t n;
    ngx_pool_large_t *大きい;
	
	// malloc を呼び出す
    p = ngx_alloc(サイズ、プール->ログ);
    (p == NULL)の場合{
        NULL を返します。
    }

    0 の場合
	// for ループは、大きなメモリ情報ブロックを格納するリンクリストを走査します。for (large = pool->large; large; large = large->next) {
        (large->alloc == NULL)の場合{
        	// 大きなメモリブロックが ngx_pfree の場合、alloc は NULL になります
        	// リンクリストを走査し、大きなメモリブロックの最初のアドレスが空の場合は、現在の malloc メモリアドレスを alloc に書き込みます。
            大きい->割り当て = p;
            p を返します。
        }
		// 4 回トラバースした後、解放された大きなメモリ ブロックの対応する情報が見つからない場合 // 効率を向上させるために、大きなメモリ ブロックの情報を保存するために、小さなメモリ ブロックのスペースを直接申請します if (n++ > 3) {
            壊す;
        }
    }
	// ポインターオフセットを介して小さなメモリプールに大規模なメモリブロック *next と *alloc を格納するためのスペースを割り当てます。large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    大きい == NULL の場合 {
    	// 小さなメモリブロック上のストレージ *next と *alloc スペースの割り当てが失敗した場合、大きなメモリブロックは記録できません // 大きなメモリブロックを解放します p
        ngx_free(p);
        NULL を返します。
    }
	
    large->alloc = p; // alloc は大きなメモリ ブロックの最初のアドレスを指します。large->next = pool->large; // これらの 2 つの文は、ヘッド挿入方式を使用して、large をヘッド ノードとしてリンク リストに新しいメモリ ブロックのレコード情報を格納します。pool->large = large;

    p を返します。
}

ここに画像の説明を挿入

大きなメモリブロックの解放

// p が指す大きなメモリブロックを解放する ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p){
    ngx_pool_large_t *l;

    (l = pool->large; l; l = l->next) {
    	// 大きなメモリブロックを格納するリンクリストを走査し、pに対応する大き​​なメモリブロックを見つける if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC、プール->ログ、0、
                           "解放: %p", l->alloc);
            // 大きなメモリブロックを解放しますが、情報が格納されているメモリ領域は解放しません ngx_free(l->alloc); // 解放
            l->alloc = NULL; // alloc は NULL に設定され、NGX_OK を返します。
        }
    }

    NGX_DECLINED を返します。
}

5. 解放されない小さなメモリブロックについて

最後のキーと終了キーは空き領域を示すために使用されます。 使用された領域を合理的にメモリプールに戻すことは不可能ですが、メモリプールはリセットされます。また、大きなメモリブロックを指すヘッダー情報とクリーンアップ関数cleanupも保存します。

nginxの効率性を考慮して、小さなメモリブロックが効率的に割り当てられ、同時にメモリがリサイクルされない

void ngx_reset_pool(ngx_pool_t *プール){
    ngx_pool_t *p;
    ngx_pool_large_t *l;
	
	// 小さいメモリをリセットする必要があり、大きいメモリの制御情報は小さいメモリに保存されるため、 // 最初に大きいメモリを解放し、次に小さいメモリをリセットする必要があります for (l = pool->large; l; l = l->next) {
        もし(l->割り当て){
            ngx_free(l->alloc);
        }
    }
	
	// 小さなメモリブロックのリンクリストを走査し、last、failed、current、chain、large などの管理情報をリセットします。 for (p = pool; p; ​​p = p->d.next) {
    	// 最初のメモリ ブロックのみに ngx_pool_data_t 以外の管理情報があるため、他のメモリ ブロックには ngx_pool_data_t の情報のみが含まれます // エラーは発生しませんが、スペースが無駄になります p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.失敗 = 0;
    }
	
	// current はメモリを割り当てるために使用できるメモリ ブロックを指します。pool->current = pool;
    プール->チェーン = NULL;
    プール->large = NULL;
}

Nginx は本質的に http サーバーです。通常は短いリンクを処理し、間接的にサービスを提供します。メモリをあまり必要としないため、メモリをリサイクルせず、リセットできます。

クライアントがリクエストを開始すると、nginx サーバーはリクエストを受信した後、応答を返します。キープアライブ時間内にクライアントから別のリクエストを受信しない場合、nginx サーバーは積極的に切断し、メモリ プールをリセットします。次にクライアント要求が届いたときに、メモリ プールを再利用できます。

長時間の接続を処理する場合、クライアントがオンラインである限り、システム リソースが使い果たされるまでサーバー リソースを解放することはできません。長いリンクでは通常、メモリの割り当てと解放に SGI STL メモリ プールが使用されますが、この方法はスペースの割り当てと再利用において nginx よりも効率が低くなります。

6. メモリプールを破壊してクリアする

次のような状況を想定します。

// メモリアラインメントが4Bであると仮定
typedef構造体{
	文字* p;
	文字データ[508];
}stデータ;

ngx_pool_t *pool = ngx_create_pool(512, log); // 合計 512B のスペースを持つ nginx メモリ ブロックを作成します。 stData* data_ptr = ngx_alloc(512); // 実際に使用可能なメモリ サイズは 512-sizeof(ngx_pool_t) であるため、大きなメモリ割り当てに属します。 data_ptr->p = malloc(10); // p は外部ヒープ メモリを指し、C++ オブジェクトでの外部リソースの使用に似ています。

大きなメモリブロックを再利用する場合、ngx_freeを呼び出すとメモリリークが発生します。

ここに画像の説明を挿入

上記のメモリリーク問題は、コールバック関数(関数ポインタを通じて実装)を通じてメモリを解放することで解決できます。

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef 構造体 ngx_pool_cleanup_s ngx_pool_cleanup_t;

// 次の構造体は ngx_pool_s.cleanup によって指し示され、これもメモリプールに格納される小さなメモリブロックです。 struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt ハンドラ;
    void *data; // 解放する必要があるリソースを指します ngx_pool_cleanup_t *next; // リソースを解放する関数はリンク リストに配置され、next はこのリンク リストを指すために使用されます};

nginx が提供する関数インターフェース:

// p はメモリプールのエントリアドレスを表し、size は p->cleanup->data ポインタのサイズを表します // p->cleanup はクリーンアップ関数情報を含む構造体を指します // ngx_pool_cleanup_add はクリーンアップ関数情報を含む構造体へのポインタを返します ngx_pool_cleanup_t* ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){
    ngx_pool_cleanup_t *c;
	
	// クリーンアップ関数の構造は、実際にはメモリプールに格納されている小さなメモリブロックです。c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    c == NULLの場合{
        NULL を返します。
    }
	
    if (サイズ) {
    	// サイズのスペースを適用します for c->data c->data = ngx_palloc(p, size);
        c->data == NULLの場合{
            NULL を返します。
        }
    } それ以外 {
        c->データ = NULL;
    }

    c->ハンドラ = NULL;
    // ヘッド挿入メソッドを使用して、pool->cleanup の後に現在の構造を文字列化します。c->next = p->cleanup;
    p->クリーンアップ = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "クリーンアップを追加: %p", c);

    c を返します。
}

方向:

void リリース(void* p){
	無料(p);
}

ngx_pool_cleanup_t* clean_ptr = ngx_clean_cleanup_add(pool, sizeof(char*));
clean_ptr->handler = &release; // ユーザーはメモリプールを破棄する前に呼び出される関数を設定します clean_ptr->data = data_ptr->p; // ユーザーはメモリプールを破棄する前に解放するメモリのアドレスを設定します ngx_destroy_pool(pool); // ユーザーはメモリプールを破棄します

7. メモリプールインターフェース関数をコンパイルしてテストする

void ngx_destroy_pool(ngx_pool_t *プール)
{
    ngx_pool_t *p、*n;
    ngx_pool_large_t *l;
    ngx_pool_cleanup_t *c;
	
	// クリーンアップリスト(解放前に呼び出す必要がある関数として保存されている)を走査して外部リソースを解放します for (c = pool->cleanup; c; c = c->next) {
        if (c->ハンドラ) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC、プール->ログ、0、
                           "クリーンアップを実行: %p", c);
            c->ハンドラ(c->データ);
        }
    }

	// 大きなメモリブロックを解放する for (l = pool->large; l; l = l->next) {
        もし(l->割り当て){
            ngx_free(l->alloc);
        }
    }
	
	// 小さなメモリプールを解放します for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);
        
        (n == NULL)の場合{
            壊す;
        }
    }
} 

ここに画像の説明を挿入

configureを実行して Makefile ファイルを生成します (エラーが報告された場合は、ソフトウェアのインストールに apt が必要であることを意味します)

ここに画像の説明を挿入

Makefile は次のとおりです。

ここに画像の説明を挿入

makeコマンドを実行してMakefileを使用してソースコードをコンパイルし、対応するディレクトリに.oファイルを生成します。

ここに画像の説明を挿入

#include <ngx_config.h>
#include <nginx.h>
#include <ngx_core.h>
#include <ngx_palloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <文字列.h>

void ngx_log_error_core(ngx_uint_t レベル、ngx_log_t *ログ、ngx_err_t エラー、
            定数char *fmt, ...){

}

typedef 構造体 Data stData;
構造体データ{
    char *ptr;
    ファイル *pfile;
};

void関数1(char *p){
    printf("ptrメモリを解放してください!\n");
    無料(p);
}

void func2(FILE *pf){
    printf("ファイルを閉じます!\n");
    fclose(pf);
}

void main(){
	// 最大 = 512 - sizeof(ngx_pool_t)
	// 合計 512 バイトのスペースを持つ nginx メモリ ブロックを作成します。 ngx_pool_t *pool = ngx_create_pool(512, NULL);
    if(プール == NULL){
        printf("ngx_create_pool が失敗しました...");
        戻る;
    }
    
	// 小さなメモリから割り当てられた void *p1 pool = ngx_palloc(pool, 128); 
    p1 == NULLの場合{
        printf("ngx_palloc 128 バイトが失敗しました...");
        戻る;
    }
	
	// stData *p2 は大容量メモリから割り当てられます pool = ngx_palloc(pool, 512); 
    p2 == NULLの場合{
        printf("ngx_palloc 512 バイトが失敗しました...");
        戻る;
    }
    
    // 外部ヒープメモリを占有 p2->ptr = malloc(12);
    strcpy(p2->ptr, "こんにちは世界");
    // ファイル記述子 p2->pfile = fopen("data.txt", "w");
    
    ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(プール、sizeof(char*));
    c1->handler = func1; // コールバック関数を設定 c1->data = p2->ptr; // リソースアドレスを設定 ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool, sizeof(FILE*));
    c2->ハンドラ = func2;
    c2->データ = p2->pファイル;
	
	// 1. 事前に設定されたすべてのクリーンアップ関数を呼び出す 2. 大きなメモリ ブロックを解放する 3. 小さなメモリ プール内のすべてのメモリを解放する ngx_destroy_pool(pool); 

    戻る;
}

ここに画像の説明を挿入

作成されたクリーンアップ ブロックは、 ngx_pool_cleanup_addでのヘッダー挿入によってpool->cleanupにリンクされるため、 ngx_destroy_poolのときに最初にファイルがクリーンアップされ、次にヒープ メモリがクリーンアップされます。

関連するテストコードは https://github.com/BugMaker-shen/nginx_sgistl_pool にプッシュされています。

nginx メモリ プールのソース コード分析に関するこの記事はこれで終わりです。より関連性の高い nginx メモリ プールのコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Nginx の基本的なメモリプール初期化構成の詳細な説明
  • Nginx ソースコードのインストール方法

<<:  HTML の rel 属性の分析

>>:  FirefoxでCookieとお気に入りをインポートおよびエクスポートする方法

推薦する

Linux システムで複数のバージョンの PHP を共存させるソリューション (超シンプル)

PHP7が出たので、最新バージョンのファンとしては、早速アップグレードして体験してみました。しかし...

Apache Web サーバーを使用して 2 つ以上のサイトを構成する方法

人気があり強力な Apache Web サーバーで 2 つ以上のサイトをホストする方法。前回の記事で...

ウェブページの再設計の7つの主要要素 ウェブページの再設計の7つの主要要素を共有する

Shopify Plus は、私たちが設立した e コマース プラットフォームのエンタープライズ バ...

Docker がデータベースのデプロイに適さない 7 つの理由のまとめ

Docker は過去 2 年間で非常に人気が高まっています。開発者はすべてのアプリケーションとソフト...

MYSQL クエリの効率を向上させる 10 の SQL ステートメント最適化テクニック

MySQL データベースの実行効率はプログラムの実行速度に大きな影響を与えます。データベースの効率的...

Docker 用ビジュアル UI 管理ツール Portainer のインストールと使用方法の分析

Portainer は、ステータス表示パネル、アプリケーション テンプレートの迅速な展開、コンテナ ...

Vueは完全な選択機能を実装しています

この記事の例では、完全な選択機能を実装するためのVueの具体的なコードを参考までに共有しています。具...

XHTML 入門チュートリアル: XHTML タグ

XHTML タグの紹介<br />おそらく、前のセクションで、XHTML ファイルと通常...

Linux でスレッドを作成するための pthread_create の具体的な使用法

pthread_create関数機能紹介pthread_createはUNIX環境のスレッド作成関数...

カルーセル例の JS 実装

この記事では、カルーセルチャートの小さなケースを実装するためのJSの具体的なコードを参考までに共有し...

JSで実現したページサイドバーの効果に関する研究

目次発見: ディスプレイアニメーションの応用実装:記事の1行目を表示する効果を実現する方法実際、その...

商品クエリ機能を実現するJavaScript

この記事の例では、商品検索機能を実現するためのJavaScriptの具体的なコードを参考までに共有し...

JavaScript の navigator.userAgent がブラウザ情報を取得するケースの説明

ブラウザはおそらく私たちにとって最も馴染みのあるツールです。 Firefox、Opera、Safar...

HTML 基本要約推奨事項 (テキスト形式)

HTMLテキスト書式タグ 標簽 描述 <b> 定義粗體文本 <em> 呈現...

CSSリンクと@importの違いの詳細な説明

HTML に CSS を追加するにはどうすればいいですか? HTML で CSS を設定する方法は ...