Linux多線程學習總結

来源:http://www.cnblogs.com/luoxn28/archive/2016/11/24/6087649.html
-Advertisement-
Play Games

線程是程式中完成一個獨立任務的完整執行序列,即一個可調度的實體;進程相當於運行中程式的一種抽象。根據運行環境的調度者的身份,線程可分為內核線程和用戶線程。內核線程,在有的系統上稱為LWP(Light Weight Process,輕量級線程),運行在內核空間,由內核調度;用戶線程運行在用戶空間,由線 ...


  線程是程式中完成一個獨立任務的完整執行序列,即一個可調度的實體;進程相當於運行中程式的一種抽象。根據運行環境的調度者的身份,線程可分為內核線程和用戶線程。內核線程,在有的系統上稱為LWP(Light Weight Process,輕量級線程),運行在內核空間,由內核調度;用戶線程運行在用戶空間,由線程庫來調度。當進程的一個內核線程獲得CPU的使用權時,它就載入並運行一個用戶線程。可見,內核線程相當於用戶線程運行的‘容器’,一個進程可以擁有M個內核線程和N個用戶線程,其中M<=N,並且一個系統的所有進程中,M和N的比值是固定的。

線程式控制制函數

pthread_create

#include <pthread.h>
int pthread_create(pthread_t * tidp, const pthread_attr_t *attr, void *(*start_rtn)(void *), void *arg);
    // 返回:成功返回0,出錯返回錯誤編號

  當pthread_create函數返回成功時,有tidp指向的記憶體被設置為新創建線程的線程ID,其類型pthread_t定義為:

#include <bits/pthreadtypes.h>
typedef unsigned long int pthread_t;

  attr參數用於設置各種不同的線程屬性,為NULL時表示預設線程屬性。新創建的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針的參數arg,如果需要向start_rtn函數傳入的參數不止一個,可以把參數放入到一個結構中,然後把這個結構的地址作為arg的參數傳入。

  線程創建時並不能保證哪個線程會先運行:是新創建的線程還是調用線程。新創建的線程可以訪問調用進程的地址空間,並且繼承調用線程的浮點環境和信號屏蔽字,但是該線程的未決信號集被清除。那什麼是未決信號呢,信號產生到信號被處理這段時間間隔,稱信號是未決的。

pthread_exit

#include <pthread.h>
void pthread_exit(void *rval_ptr);
    // 線程終止

  線程在結束時最好調用該函數,以確保全全、乾凈的退出。pthread_exit函數通過rval_ptr參數向調用線程的回收者傳遞退出信息,進程中的其他線程可以調用pthread_join函數訪問到這個指針。pthread_exit執行完後不會返回到調用者,而且永遠不會失敗。

線程可以通過以下三種方式退出,在不終止整個進程的情況下停止它的控制流:

  • 線程只是從啟動過程中退出,返回值是線程的退出碼
  • 線程可以被同一進程中的其他線程取消
  • 線程調用pthread_exit

pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
    // 返回:成功返回0,出錯返回錯誤代碼

  thread是目標線程標識符,rval_ptr指向目標線程返回時的退出信息,該函數會一直阻塞,直到被回收的線程結束為止。可能的錯誤碼為:

pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);
    // 返回:成功返回0,出錯返回錯誤代碼

  預設情況下,pthread_cancel函數會使有thread標識的線程的表現為如同調用了參數為PTHREAD_CANCEL的pthread_exit函數,但是,接收到取消請求的目標線程可以決定是否允許被取消以及如何取消,這分別由以下兩個函數來控制:

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldstate);

  註意pthread_cancel並不等待線程結束,它只是提出請求。

 

互斥量

  互斥量本質是一把鎖,在訪問公共資源前對互斥量設置(加鎖),確保同一時間只有一個線程訪問數據,在訪問完成後再釋放(解鎖)互斥量。在互斥量加鎖之後,其他線程試圖對該互斥量再次加鎖時都會被阻塞,知道當前線程釋放互斥鎖。如果釋放互斥量時有一個以上的互斥量,那麼所有在該互斥量上阻塞的線程都會變成可運行狀態,第一個變成運行的線程可以對互斥量加鎖,其他線程看到互斥量依然是鎖著的,只能再次阻塞等待該互斥量。

  互斥量用pthread_mutex_t數據類型表示,在使用互斥量之前,必須使用pthread_mutex_init函數對它進行初始化,註意,使用完畢後需調用pthread_mutex_destroy。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
    // 兩個函數返回值,成功返回0,否則返回錯誤碼

  pthread_mutex_init用於初始化互斥鎖,mutexattr用於指定互斥鎖的屬性,若為NULL,則表示預設屬性。除了用這個函數初始化互斥所外,還可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER。
  pthread_mutex_destroy用於銷毀互斥鎖,以釋放占用的內核資源,銷毀一個已經加鎖的互斥鎖將導致不可預期的後果。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
    // 成功返回0,否則返回錯誤碼

  pthread_mutex_lock以原子操作給一個互斥鎖加鎖。如果目標互斥鎖已經被加鎖,則pthread_mutex_lock則被阻塞,直到該互斥鎖占有者把它給解鎖。
  pthread_mutex_trylock和pthread_mutex_lock類似,不過它始終立即返回,而不論被操作的互斥鎖是否加鎖,是pthread_mutex_lock的非阻塞版本。當目標互斥鎖未被加鎖時,pthread_mutex_trylock進行加鎖操作;否則將返回EBUSY錯誤碼。註意:這裡討論的pthread_mutex_lock和pthread_mutex_trylock是針對普通鎖而言的,對於其他類型的鎖,這兩個加鎖函數會有不同的行為。
  pthread_mutex_unlock以原子操作方式給一個互斥鎖進行解鎖操作。如果此時有其他線程正在等待這個互斥鎖,則這些線程中的一個將獲得它。

互斥鎖使用示例:

/**
 * 使用3個線程分別列印 A B C
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

typedef struct ThreadInfo_t
{
    char info; /* 'A' or 'B' or 'C' */
    int  n;    /* remainder num */
    int  num;  /* share num */
    pthread_mutex_t mutex;
}ThreadInfo;

void *func(void *arg)
{
    int cnt    = 3;
    ThreadInfo *info = (ThreadInfo *)arg;
    int  result = info->n;
    char show   = info->info;

    while (cnt > 0) {
        if (info->num % 3 == result) {
            printf("---%c\n", show);

            pthread_mutex_lock(&info->mutex);
            info->num++;
            cnt--;
            pthread_mutex_unlock(&info->mutex);
        }
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;
    ThreadInfo info;

    memset(&info, 0, sizeof(ThreadInfo));
    pthread_mutex_init(&(info.mutex), NULL);

    info.n = 0;
    info.info = 'A';
    pthread_create(&t1, NULL, func, &info);
    sleep(1);

    info.n = 1;
    info.info = 'B';
    pthread_create(&t2, NULL, func, &info);
    sleep(1);

    info.n = 2;
    info.info = 'C';
    pthread_create(&t3, NULL, func, &info);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    return 0;
}

 

讀寫鎖

  讀寫鎖和互斥體類似,不過讀寫鎖有更高的並行性,互斥體要麼是鎖住狀態,要麼是不加鎖狀態,而且一次只有一個線程可以對其加鎖。而讀寫鎖可以有3個狀態,讀模式下鎖住狀態,寫模式下鎖住狀態,不加鎖狀態。一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占用讀模式的讀寫鎖。讀寫鎖適合對數據結構讀的次數遠大於寫的情況。

  當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖是讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是任何希望以寫模式對此鎖進行加鎖的線程都會阻塞,直到所有的線程釋放它們的讀鎖為止。

#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockaddr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);
    // 成功返回0,否則返回錯誤碼

  通過pthread_rwlock_init初始化讀寫鎖,如果希望讀寫鎖有預設屬性,可以傳一個NULL指針給attr。當不再需要讀寫鎖時,調用pthread_rwlock_destroy做清理工作。

#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock);
    // 成功返回0,否則返回錯誤碼

  讀寫鎖的讀加鎖、寫加鎖和解鎖操作。

讀寫鎖程式示例:

/**
 * 兩個讀線程讀取數據,一個寫線程更新數據
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define READ_THREAD  0
#define WRITE_THREAD 1

int g_data = 0;
pthread_rwlock_t g_rwlock;

void *func(void *pdata)
{
    int data = (int)pdata;

    while (1) {
        if (READ_THREAD == data) {
            pthread_rwlock_rdlock(&g_rwlock);
            printf("-----%d------ %d\n", pthread_self(), g_data);
            sleep(1);
            pthread_rwlock_unlock(&g_rwlock);
            sleep(1);
        }
        else {
            pthread_rwlock_wrlock(&g_rwlock);
            g_data++;
            printf("add the g_data\n");
            pthread_rwlock_unlock(&g_rwlock);
            sleep(1);
        }
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;

    pthread_rwlock_init(&g_rwlock, NULL);

    pthread_create(&t1, NULL, func, (void *)READ_THREAD);
    pthread_create(&t2, NULL, func, (void *)READ_THREAD);
    pthread_create(&t3, NULL, func, (void *)WRITE_THREAD);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    pthread_rwlock_destroy(&g_rwlock);

    return 0;
}

 

條件變數

  條件變數是線程可用的一種同步機制,條件變數給多個線程提供了一個回合的場所,條件變數和互斥量一起使用,允許線程以無競爭的方式等待特定的條件發生。條件變數本事是由互斥體保護的,線程在改變條件狀態之前必須首先鎖住互斥量,其他線程在獲取互斥量之前就不會覺察到這種變化,因為互斥量必須鎖定之後才改變條件。

#include<pthread.h>
pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_destroy(pthread_cont_t *cond);
    // 成功返回0,否則返回錯誤碼

  使用條件變數前調用pthread_cond_init初始化,使用完畢後調用pthread_cond_destroy做清理工作。除非需要創建一個具有非預設屬性的條件變數,否則pthread_cond_init函數的attr參數可以設置為NULL。

#include<pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    // 成功返回0,否則返回錯誤碼

  傳遞給pthread_cond_wait的互斥量對條件進行保護,調用者把鎖住互斥量傳給函數,函數然後自動把調用線程放到等待條件的線程列表上,對互斥量解鎖。這就關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操作之間的時間通道,這樣線程就不會錯過條件的任何變化。pthread_cond_wait函數返回時,互斥量再次被鎖住。

  pthread_cond_broadcast用廣播的形式喚醒所有等待條件變數的線程。pthread_cond_signal用於喚醒一個等待條件變數的線程,至於哪個線程被喚醒,取決於線程的優先順序和調度機制。有時候需要喚醒一個指定的線程,但pthread沒有對該需要提供解決方法。可以間接實現該需求:定義一個能夠唯一表示目標線程的全局變數,在喚醒等待條件變數的線程前先設置該變數為目標線程,然後以廣播形式喚醒所有等待條件變數的線程,這些線程被喚醒後都檢查改變數是否是自己,如果是就開始執行後續代碼,否則繼續等待。

條件變數程式示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define err_sys(msg) \
    do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
    do { fprintf(stderr, msg); exit(-1); } while(0)

pthread_cond_t cond;

void *r1(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;

    while(cnt--)
    {
        printf("r1: I am wait.\n");
        pthread_mutex_lock(mutex);
        pthread_cond_wait(&cond, mutex); /* mutex參數用來保護條件變數的互斥鎖,調用pthread_cond_wait前mutex必須加鎖 */
        pthread_mutex_unlock(mutex);
    }
    return "r1 over";
}

void *r2(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;

    while(cnt--)
    {
        //pthread_mutex_lock(mutex); //這個地方不用加鎖操作就行
        printf("r2: I am send the cond signal.\n");
        pthread_cond_signal(&cond);
        //pthread_mutex_unlock(mutex);
        sleep(1);
    }
    return "r2 over";
}

int main(void)
{
    pthread_mutex_t mutex;
    pthread_t t1, t2;
    char* p1 = NULL;
    char* p2 = NULL;
    
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&t1, NULL, r1, &mutex);
    pthread_create(&t2, NULL, r2, &mutex);

    pthread_join(t1, (void **)&p1);
    pthread_join(t2, (void **)&p2);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
    printf("s1: %s\n", p1);
    printf("s2: %s\n", p2);

    return 0;
}

 

自旋鎖

  自旋鎖和互斥量類似,但它不是通過休眠使進程阻塞,而是在獲取鎖之前一直處於忙等(自旋)狀態,自旋鎖可用於下麵的情況:鎖被持有的時間短,並且線程不希望再重新調度上花費太多的成本。自旋鎖通常作為底層原語用於實現其他類型的鎖。根據他們所基於的系統架構,可以通過使用測試並設置指令有效地實現。當然這裡說的有效也還是會導致CPU資源的浪費:當線程自旋鎖變為可用時,CPU不能做其他任何事情,這也是自旋鎖只能夠被只有一小段時間的原因。

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

  pshared參數表示進程共用屬性,表明自旋鎖是如何獲取的,如果它設為PTHREAD_PROCESS_SHARED,則自旋鎖能被可以訪問鎖底層記憶體的線程所獲取,即使那些線程屬於不同的進程。否則pshared參數設為PTHREAD_PROCESS_PROVATE,自旋鎖就只能被初始化該鎖的進程內部的線程訪問到。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

  如果自旋鎖當前在解鎖狀態,pthread_spin_lock函數不要自旋就可以對它加鎖,試圖對沒有加鎖的自旋鎖進行解鎖,結果是未定義的。需要註意,不要在持有自旋鎖情況下可能會進入休眠狀態的函數,如果調用了這些函數,會浪費CPU資源,其他線程需要獲取自旋鎖需要等待的時間更長了。

自旋鎖使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_spinlock_t g_lock;
int g_data = 0;

void *func(void *arg)
{
    while (1) {
        pthread_spin_lock(&g_lock);
        g_data++;
        printf("----------- %d\n", g_data);
        sleep(1);
        pthread_spin_unlock(&g_lock);
    }
}

int main(int argc, char **argv)
{
    pthread_t tid;
    pthread_spin_init(&g_lock, PTHREAD_PROCESS_PRIVATE);

    pthread_create(&tid, NULL, func, NULL);
    pthread_create(&tid, NULL, func, NULL);
    pthread_create(&tid, NULL, func, NULL);

    pthread_join(tid, NULL);

    return 0;
}

 

屏障

  屏障是用戶協調多個線程並行工作的同步機制,屏障允許每個線程等待,直到所有合作的線程都到達某一點,然後從該點出繼續執行。pthread_join其實就是一種屏障,允許一個線程等待,直到另一個線程退出。但是屏障對象的概念更廣,它們允許任意數量的線程等待,直到所有的線程完成處理工作,而線程不需要退出,所有線程達到屏障後可以繼續工作。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
    // 成功返回0,否則返回錯誤編號

  初始化屏障時,可以使用count參數指定,在允許所有線程繼續運行前,必須達到屏障的線程數目。attr指定屏障屬性,NULL為預設屬性。

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
    // 成功返回0,否則返回錯誤編號

  可以使用pthread_barrier_wait函數來表明,線程已完成工作,準備等所有其他線程趕過來。調用pthread_barrier_wait的線程在屏障計數未滿足條件時,會進入休眠狀態。如果該線程是最後一個調用pthread_barrier_wait的線程,則所有的線程會被喚醒。

  一旦到達屏障計數值,而且線程處於非阻塞狀態,屏障就可以被重覆使用。

屏障使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_barrier_t g_barrier;

void *func(void *arg)
{
    int id = (int )arg;

    if (id == 0) {
        printf("thread 0\n");
        sleep(1);
        pthread_barrier_wait(&g_barrier);
        printf("thread 0 come...\n");
    }
    else if (id == 1) {
        printf("thread 1\n");
        sleep(2);
        pthread_barrier_wait(&g_barrier);
        printf("thread 1 come...\n");    
    }
    else if (id == 2) {
        printf("thread 2\n");
        sleep(3);
        pthread_barrier_wait(&g_barrier);
        printf("thread 2 come...\n");
    }

    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3;
    pthread_barrier_init(&g_barrier, NULL, 3);

    pthread_create(&t1, NULL, func, (void *)0);
    pthread_create(&t2, NULL, func, (void *)1);
    pthread_create(&t3, NULL, func, (void *)2);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    return 0;
}

 

參考:

  1、《UNIX環境高級編程 第三版》線程章節

  2、ThinkInTechnology


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • mysql簡介 1、什麼是資料庫 ? 資料庫(Database)是按照數據結構來組織、存儲和管理數據的倉庫,它產生於距今六十多年前,隨著信息技術和市場的發展,特別是二十世紀九十年代以後,數據管理不再僅僅是存儲和管理數據,而轉變成用戶所需要的各種數據管理的方式。資料庫有很多種類型,從最簡單的存儲有各種 ...
  • 本篇學習筆記的主要內容: 介紹MySQL支持的各種數據類型(常用),並講解其主要特點。 MySQL支持多種數據類型,主要包括數值類型、日期和時間類型、字元串類型。 數值類型 MySQL的數值類型包括整數類型、浮點數類型、定點數類型、位類型。 整數類型 MySQL支持的整數類型有tinyint、sma ...
  • 計算欄位 如果想在一個欄位中既顯示公司的名稱,又顯示公司的地址,但是這兩個信息一般包含在不同的表列中 城市、州和郵政編碼存儲在不同的列中,但是郵件標簽列印程式卻需要把它們作為一個恰當格式的欄位檢索出來 物品訂單表存儲物品的價格和數量,但不需要存儲每個物品的總價格,為了列印發票,需要物品的總價 需要根 ...
  • CSDN課程 http://edu.csdn.net/courses/o364 ...
  • 用通配符進行過濾 like操作符 %通配符 %可以匹配任意字元 下劃線通配符 下劃線只可以匹配一個字元 用正則表達式進行搜索 基本字元匹配 檢索prod_name包含文本1000的所有行 註意 SELECT prod_name FROM products WHERE prod_name LIKE ' ...
  • Northwind 示例資料庫下載: NORTHWND.MDF (PS:開始試過sql2012直接附加失敗) 新建查詢-執行下麵代碼 *********************************************************************************** ...
  • Identity是標識值,在SQL Server中,有ID列,ID屬性,ID值,ID列的值等術語。 Identity屬性是指在創建Table時,為列指定的Identity屬性,其語法是:column_name type IDENTITY [ (seed , increment) ],Identity ...
  • 集群發現機制 在Ignite中的集群號稱是無中心的,而且支持命令行啟動和嵌入應用啟動,所以按理說很簡單。而且集群有自動發現機制感覺對於懶人開發來說太好了,抱著試一試的心態測試一下吧。 在Apache Ignite中有三種自有的發現機制:組播、靜態IP、組播+靜態IP。下麵就這幾種來試一試吧。 測試方 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...