生產者-消費者問題

来源:https://www.cnblogs.com/songhe364826110/archive/2019/09/15/11522117.html
-Advertisement-
Play Games

[TOC] 1. 概述 定義 生產者消費者問題是線程同步的經典問題,也稱為有界緩衝區問題,問題描述大致如下: 生產者和消費者之間共用一個有界數據緩衝區 一個或多個生產者(線程或進程)向緩衝區放置數據 一個或多個消費者(線程或進程)從緩衝區取出數據 緩衝區 生產者消費者問題中的緩衝區,包括隊列緩衝區和 ...


目錄

1. 概述

定義

生產者消費者問題是線程同步的經典問題,也稱為有界緩衝區問題,問題描述大致如下:

  • 生產者和消費者之間共用一個有界數據緩衝區
  • 一個或多個生產者(線程或進程)向緩衝區放置數據
  • 一個或多個消費者(線程或進程)從緩衝區取出數據

緩衝區

生產者消費者問題中的緩衝區,包括隊列緩衝區和環形緩衝區,它們都按照先進先出的順序處理數據,我們現在只考慮隊列緩衝區:

  • 隊列緩衝區通常使用普通的隊列數據結構
  • 隊列內部實現可以是鏈表或數組

緩衝區有兩個極端狀態:緩衝區空,緩衝區滿。鏈表隊列和數組隊列緩衝區空的含義相同,都是隊列中沒有一個元素的情形,但兩者緩衝區滿的含義不同:

  • 數組隊列在初始化時就必須指定最大容量,緩衝區滿的條件很清晰
  • 鏈表隊列沒有最大容量的概念,需要人為指定

此外,Posix消息隊列也可以作為隊列緩衝區,Posix當以無優先順序消息的方式使用時,也是按照先進先出的順序進行處理的。
本文只討論第一種數據結構隊列緩衝區,基於Posix消息隊列緩衝區的生產者消費者問題,會在後續Posix消息隊列中單獨講解。

2. 典型模型

生產者消費者個數的多少、緩衝區的類型都會影響生產者消費者問題模型的複雜度,本文選取兩種常見典型模型進行分析。

模型一

  • 單個生產者 + 單個消費者
  • 生產者線程啟動後,立即創建消費者線程
  • 緩衝區容量有限,且小於數據條目數量

該模型只需要處理生產者和消費者之間的同步問題,在實際工程很常見,具體的同步詳情為:

  • 當緩衝區為空時,消費者不能從其中取出數據
  • 當緩衝區為滿時,生產者不能向其中寫入數據

模型二

  • 多個生產者 + 單個消費者
  • 生產者線程啟動後,立即創建消費者線程
  • 緩衝區容量有限,且小於數據條目數量

模型二與模型一相比,既需要處理生產者之間的同步問題,又需要處理生產者和消費者之間的同步問題,在實際工程也比較常見,具體的同步詳情為:

  • 同時只能有一個生產者向緩衝區寫入數據
  • 當緩衝區為空時,消費者不能從其中取出數據
  • 當緩衝區為滿時,生產者不能向其中寫入數據

可選需求

模型一和模型二所列均為必須處理的同步問題,還有一個根據實際情況、可能會存在的同步需求:

  • 共用數據中包含描述緩衝區當前狀態的變數,如下標、計數、鏈表等
  • 生產者和消費者在讀寫緩衝區後都需要更新緩衝區狀態變數
  • 若滿足上述兩個條件,則同時只能有一個生產者或消費者可以進行緩衝區操作與狀態變數更新

隊列緩衝區,不管是數組實現還是鏈表實現,其內部都符合上述條件,都需要處理該可選同步需求。

3. 數據結構隊列C語言實現

網上找了份數據結構隊列C語言實現的代碼,稍微改了下,可正常使用,本節後續的生產者消費者問題示例代碼就是用它作為緩衝區。

#ifndef _LINK_QUEUE_H_
#define _LINK_QUEUE_H_

typedef enum
{
    false = 0,
    true
} bool;

typedef int data_t;

typedef struct LinkNode
{
    data_t data;
    struct LinkNode *next;
} LinkNode, *LinkQueue;

typedef struct
{
    LinkQueue front;
    LinkQueue rear;
} HeadQueue;

HeadQueue *CreateEmptyQueue();
bool EmptyLinkQueue(HeadQueue *queue);
void EnQueue(HeadQueue *queue, data_t value);
void DeQueue(HeadQueue *queue, data_t *value);
void PrintQueue(HeadQueue *queue);
bool ClearLinkQueue(HeadQueue *queue);
bool DestroyLinkQueue(HeadQueue *queue);
int  GetCurItemsNum(HeadQueue *queue);

#endif
#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>

static int nitems;

//創建空鏈表隊列
HeadQueue *CreateEmptyQueue(void)
{
    HeadQueue *queue = (HeadQueue *)malloc(sizeof(HeadQueue));

    if (queue == NULL)
    {
        perror("Create empty queue failed");
        exit(EXIT_FAILURE);
    }

    queue->rear = queue->front = NULL;
    nitems = 0;

    return queue;
}

//判斷是否為空鏈表隊列
bool EmptyLinkQueue(HeadQueue *queue)
{
    if (queue == NULL)
    {
        printf("Empty link queue error!\n");
        exit(EXIT_FAILURE);
    }

    return queue->front == NULL ? true : false;
}

//增加隊列元素
void EnQueue(HeadQueue *queue, data_t value)
{
    LinkQueue new;

    if (queue == NULL)
    {
        printf("EnQueue Error!\n");
        return;
    }

    new = (LinkQueue)malloc(sizeof(LinkNode));

    if (new == NULL)
    {
        perror("Insert value failed");
        return;
    }

    new->data = value;
    new->next = NULL;

    if (EmptyLinkQueue(queue))
    {
        queue->front = queue->rear = new;
    }
    else
    {
        queue->rear->next = new;
        queue->rear = new;
    }

    nitems++;
}

//刪除隊列元素
void DeQueue(HeadQueue *queue, data_t *value)
{
    *value = 0;
    LinkQueue remove;

    if (queue == NULL)
    {
        printf("DeQueue error!\n");
        return;
    }

    if (EmptyLinkQueue(queue))
    {
        printf("queue is empty!\n");
        return;
    }

    remove = queue->front;
    queue->front = remove->next;

    if (queue->front == NULL)
        queue->rear = NULL;

    *value = remove->data;
    free(remove);

    nitems--;
}

//遍歷隊列元素
void PrintQueue(HeadQueue *queue)
{
    LinkQueue node;

    printf("queue = {");
    node = queue->front;

    if (node == NULL)
    {
        printf("}\n");
        return ;
    }

    while (node != NULL)
    {
        printf("%d,", node->data);
        node = node->next;
    }

    printf("\b}\n");
}

//清空隊列元素
bool ClearLinkQueue(HeadQueue *queue)
{
    LinkQueue remove = queue->front;

    while (remove != NULL)
    {
        queue->front = queue->front->next;
        free(remove);
        remove = queue->front;
    }

    queue->front = NULL;
    queue->rear = NULL;
    nitems = 0;

    return true;
}

//銷毀隊列
bool DestroyLinkQueue(HeadQueue *queue)
{
    if (queue != NULL)
    {
        ClearLinkQueue(queue);
        free(queue);
        nitems = 0;
        return true;
    }
    else
    {
        printf("DestroyLinkQueue error!\n");
        return false;
    }
}

//獲得當前隊列元素個數
int GetCurItemsNum(HeadQueue *queue)
{
    return nitems;
}

4. 代碼實現——互斥鎖 + 條件變數

#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <pthread.h>

#define MAX_THREADS   10
#define MAX_ITEMS     1000000
#define MAX_BUFFER    10

/*
 * 編程技巧:儘量把共用數據和它們的同步變數(互斥鎖、條件變數、信號量)收集到同一個結構體中
*/
struct Shared
{
    pthread_cond_t  cond_nempty;  //條件變數:緩衝區不滿
    pthread_cond_t  cond_nstored; //條件變數:緩衝區不空
    pthread_mutex_t cond_mutex;   //保護條件的鎖,用於確保同時只有一個線程可以訪問緩衝區
    pthread_mutex_t mutex;        //同步多個生產者的鎖,僅在有多個生產者時使用
    HeadQueue *queue;             //隊列緩衝區
    int nput;
    int nval;
};

struct Shared shared;

void shared_init()
{
    shared.queue = CreateEmptyQueue();
    pthread_mutex_init(&shared.mutex, NULL);
    pthread_mutex_init(&shared.cond_mutex, NULL);
    pthread_cond_init(&shared.cond_nempty, NULL);
    pthread_cond_init(&shared.cond_nstored, NULL);
}

void shared_destroy()
{
    DestroyLinkQueue(shared.queue);
    pthread_mutex_destroy(&shared.mutex);
    pthread_mutex_destroy(&shared.cond_mutex);
    pthread_cond_destroy(&shared.cond_nempty);
    pthread_cond_destroy(&shared.cond_nstored);
}

void *produce(void *arg)
{
    int nthreads = *((int *)arg);

    while (1)
    {
        if (shared.nput >= MAX_ITEMS)
        {
            pthread_exit(NULL);
        }

        if (nthreads > 1)
        {
            pthread_mutex_lock(&shared.mutex);
        }

        pthread_mutex_lock(&shared.cond_mutex);

        while (GetCurItemsNum(shared.queue) == MAX_BUFFER)
            pthread_cond_wait(&shared.cond_nempty, &shared.cond_mutex);

        EnQueue(shared.queue, shared.nval);

        shared.nput++;
        shared.nval++;

        pthread_cond_signal(&shared.cond_nstored);

        if (nthreads > 1)
        {
            pthread_mutex_unlock(&shared.mutex);
        }

        pthread_mutex_unlock(&shared.cond_mutex);
    }

    pthread_exit(NULL);
}

void *consume(void *arg)
{
    int nval;
    int i;

    for (i = 0; i < MAX_ITEMS; i++)
    {
        pthread_mutex_lock(&shared.cond_mutex);

        while (GetCurItemsNum(shared.queue) == 0)
            pthread_cond_wait(&shared.cond_nstored, &shared.cond_mutex);

        DeQueue(shared.queue, &nval);

        pthread_cond_signal(&shared.cond_nempty);
        pthread_mutex_unlock(&shared.cond_mutex);

        if (nval != i)
        {
            printf("error: buff[%d] = %d\n", i, nval);
        }
    }

    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t tid_produce[MAX_THREADS];
    pthread_t tid_consume;
    int nthreads;
    struct timeval start_time;
    struct timeval end_time;
    float time_sec;
    int i;

    nthreads = (atoi(argv[1]) > MAX_THREADS) ? MAX_THREADS : atoi(argv[1]);
    shared_init();
    gettimeofday(&start_time, NULL);

    for (i = 0; i < nthreads; i++)
    {
        pthread_create(&tid_produce[i], NULL, produce, &nthreads);
    }

    pthread_create(&tid_consume, NULL, consume, NULL);

    for (i = 0; i < nthreads; i++)
    {
        pthread_join(tid_produce[i], NULL);
    }

    pthread_join(tid_consume, NULL);

    gettimeofday(&end_time, NULL);
    time_sec = (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_usec - start_time.tv_usec) / 1000000.0;
    printf("%d produce and %d consume total spend %.2f second\n", nthreads, 1, time_sec);

    shared_destroy();

    return 0;
}

運行時通過命令行參數指定生產者個數,來選擇模型一或模型二,其中,在produce()中,第57-56行、第74-77行會根據生產者個數,選擇是否使用第二把鎖。
下麵分別是模型一和模型二的運行結果。

5. 代碼實現——互斥鎖 + Posix有名信號量

#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <pthread.h>
#include <semaphore.h>

#define SEM_NEMPTY    "/sem_nempty"
#define SEM_NSTROED   "/sem_nstored"

#define MAX_THREADS   10
#define MAX_ITEMS     1000000
#define MAX_BUFFER    10

/*
 * 編程技巧:把共用數據和它們的同步變數(互斥鎖、條件變數、信號量)收集到同一個結構體中
*/
struct Shared
{
    pthread_mutex_t mutex;
    sem_t *nempty;
    sem_t *nstored;
    HeadQueue *queue;
    int nput;
    int nval;
};

struct Shared shared;

void shared_init()
{
    shared.queue = CreateEmptyQueue();
    pthread_mutex_init(&shared.mutex, NULL);
    shared.nempty = sem_open(SEM_NEMPTY,  O_CREAT, 0666, MAX_BUFFER);
    shared.nstored = sem_open(SEM_NSTROED, O_CREAT, 0666, 0);
}

void shared_destroy()
{
    DestroyLinkQueue(shared.queue);
    pthread_mutex_destroy(&shared.mutex);
    sem_close(shared.nempty);
    sem_close(shared.nstored);
    sem_unlink(SEM_NEMPTY);
    sem_unlink(SEM_NSTROED);
}

void *produce(void *arg)
{
    while (1)
    {
        if (shared.nput >= MAX_ITEMS)
        {
            pthread_exit(NULL);
        }

        /*
         * produce和consume都必須先sem_wait,確保sem_wait返回後再上鎖;
         * 防止先上鎖後sem_wait阻塞,導致另一方二次上鎖而死鎖.
        */
        sem_wait(shared.nempty);
        pthread_mutex_lock(&shared.mutex);

        EnQueue(shared.queue, shared.nval);

        /* 如果隊列緩衝區中的元素個數超過了MAX_BUFFER,就輸出提示信息 */
        if (GetCurItemsNum(shared.queue) > MAX_BUFFER)
        {
            printf("notice: queue buffer capacity > %d\n", MAX_BUFFER);
        }

        shared.nput++;
        shared.nval++;

        pthread_mutex_unlock(&shared.mutex);
        sem_post(shared.nstored);
    }

    pthread_exit(NULL);
}

void *consume(void *arg)
{
    int nval;
    int i;

    for (i = 0; i < MAX_ITEMS; i++)
    {
        /*
         * produce和consume都必須先sem_wait,確保sem_wait返回後再上鎖;
         * 防止先上鎖後sem_wait阻塞,導致另一方二次上鎖而死鎖.
        */
        sem_wait(shared.nstored);
        pthread_mutex_lock(&shared.mutex);

        DeQueue(shared.queue, &nval);

        if (nval != i)
        {
            printf("error: buff[%d] = %d\n", i, nval);
        }

        pthread_mutex_unlock(&shared.mutex);
        sem_post(shared.nempty);
    }

    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t tid_produce[MAX_THREADS];
    pthread_t tid_consume;
    int nthreads;
    struct timeval start_time;
    struct timeval end_time;
    float time_sec;
    int i;

    nthreads = (atoi(argv[1]) > MAX_THREADS) ? MAX_THREADS : atoi(argv[1]);
    shared_init();
    gettimeofday(&start_time, NULL);

    for (i = 0; i < nthreads; i++)
    {
        pthread_create(&tid_produce[i], NULL, produce, &nthreads);
    }

    pthread_create(&tid_consume, NULL, consume, NULL);

    for (i = 0; i < nthreads; i++)
    {
        pthread_join(tid_produce[i], NULL);
    }

    pthread_join(tid_consume, NULL);

    gettimeofday(&end_time, NULL);
    time_sec = (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_usec - start_time.tv_usec) / 1000000.0;
    printf("%d produce and %d consume total spend %.2f second\n", nthreads, 1, time_sec);

    shared_destroy();

    return 0;
}

運行時通過命令行參數指定生產者個數,來選擇模型一或模型二。

6. 代碼實現——互斥鎖 + Posix無名信號量

#include "linkqueue.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <pthread.h>
#include <semaphore.h>

#define MAX_THREADS   10
#define MAX_ITEMS     1000000
#define MAX_BUFFER    10

/*
 * 編程技巧:把共用數據和它們的同步變數(互斥鎖、條件變數、信號量)收集到同一個結構體中
*/
struct Shared
{
    pthread_mutex_t mutex;
    sem_t nempty;
    sem_t nstored;
    HeadQueue *queue;
    int nput;
    int nval;
};

struct Shared shared;

void shared_init()
{
    shared.queue = CreateEmptyQueue();
    pthread_mutex_init(&shared.mutex, NULL);
    sem_init(&shared.nempty, 0, MAX_BUFFER);
    sem_init(&shared.nstored, 0, 0);
}

void shared_destroy()
{
    DestroyLinkQueue(shared.queue);
    pthread_mutex_destroy(&shared.mutex);
    sem_destroy(&shared.nempty);
    sem_destroy(&shared.nstored);
}

void *produce(void *arg)
{
    while (1)
    {
        if (shared.nput >= MAX_ITEMS)
        {
            pthread_exit(NULL);
        }

        /*
         * produce和consume都必須先sem_wait,確保sem_wait返回後再上鎖;
         * 防止先上鎖後sem_wait阻塞,導致另一方二次上鎖而死鎖.
        */
        sem_wait(&shared.nempty);
        pthread_mutex_lock(&shared.mutex);

        EnQueue(shared.queue, shared.nval);

        /* 如果隊列緩衝區中的元素個數超過了MAX_BUFFER,就輸出提示信息 */
        if (GetCurItemsNum(shared.queue) > MAX_BUFFER)
        {
            printf("notice: queue buffer capacity > %d\n", MAX_BUFFER);
        }

        shared.nput++;
        shared.nval++;

        pthread_mutex_unlock(&shared.mutex);
        sem_post(&shared.nstored);
    }

    pthread_exit(NULL);
}

void *consume(void *arg)
{
    int nval;
    int i;

    for (i = 0; i < MAX_ITEMS; i++)
    {
        /*
         * produce和consume都必須先sem_wait,確保sem_wait返回後再上鎖;
         * 防止先上鎖後sem_wait阻塞,導致另一方二次上鎖而死鎖.
        */
        sem_wait(&shared.nstored);
        pthread_mutex_lock(&shared.mutex);

        DeQueue(shared.queue, &nval);

        if (nval != i)
        {
            printf("error: buff[%d] = %d\n", i, nval);
        }

        pthread_mutex_unlock(&shared.mutex);
        sem_post(&shared.nempty);
    }

    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t tid_produce[MAX_THREADS];
    pthread_t tid_consume;
    int nthreads;
    struct timeval start_time;
    struct timeval end_time;
    float time_sec;
    int i;

    nthreads = (atoi(argv[1]) > MAX_THREADS) ? MAX_THREADS : atoi(argv[1]);
    shared_init();
    gettimeofday(&start_time, NULL);

    for (i = 0; i < nthreads; i++)
    {
        pthread_create(&tid_produce[i], NULL, produce, &nthreads);
    }

    pthread_create(&tid_consume, NULL, consume, NULL);

    for (i = 0; i < nthreads; i++)
    {
        pthread_join(tid_produce[i], NULL);
    }

    pthread_join(tid_consume, NULL);

    gettimeofday(&end_time, NULL);
    time_sec = (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_usec - start_time.tv_usec) / 1000000.0;
    printf("%d produce and %d consume total spend %.2f second\n", nthreads, 1, time_sec);

    shared_destroy();

    return 0;
}

運行時通過命令行參數指定生產者個數,來選擇模型一或模型二。

7. 效率對比

結論

  • 只有Posix無名信號量使用多生產者獲得了效率正提升,其餘都是負提升
  • 單生產者效率,Posix有名信號量 > Posix無名信號量 > 條件變數
  • 多生產者效率,Posix無名信號量 > Posix有名信號量 > 條件變數

該結論僅限於本文使用的示例代碼,僅作結果陳述,不代表具有普適性,也沒有進行深層原因分析。

奇怪的問題

在使用條件變數的示例代碼中,當有多個生產者時用了兩把鎖:

  • mutex同步多個生產者,確保多個生產者不會同時訪問緩衝區
  • cond_mutex確保生產者消費者不會同時訪問緩衝區

實際上,只需要一個cond_mutex就夠了,但經過測試發現這樣運行時間會明顯增加,如下圖所示:

先從理論上分析下,10個生產者1個消費者:

  • 只能同時有一個生產者訪問緩衝區
  • 只能同時有一個生產者或消費者訪問緩衝區

總結起來就是:只能同時有一個線程訪問緩衝區。從這個結論來看,確實不需要第二把鎖,在一把鎖就夠用的情況下,再加第二把鎖反而多了不必要的開銷。
經過測試,Posix有名和無名信號量都符合這個理論,運行時間明顯增加了,有名信號量從1.9s增加到2.3s,無名信號量從1.4s增加到2.5s。
但邪門就邪門在,條件變數卻相反,一把鎖效率低,兩把鎖反而效率有大幅提升,想不通為什麼!


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

-Advertisement-
Play Games
更多相關文章
  • 一、前言 上一篇【分層架構設計】我們已經有了架構的輪廓,現在我們就在這個輪廓裡面造輪子。項目要想開始,肯定先得確定ORM框架,目前市面上的ORM框架有很多,對於.net人員來說很容易就想到以ADO.NET為基礎所發展出來的ORM框架EntityFramework。不得不說EntityFramewor ...
  • // 當時我裝這個也是折騰了一下午 , 所以寫一個筆記記錄一下; //如果哪裡有問題的話我們可以一起討論( qq: 2970911340,郵箱[email protected]),這也是我第一次寫博客 練練手 1. 安cmake工具 # yum install -y cmake 2. 創建mysql用戶 #usera ...
  • 一、Linux系統簡介 通過實驗一瞭解了Linux 的歷史,Linux與windows之間的區別以及學習Linux的方法。因為一直用的都是windows系統,習慣了圖形界面,而Linux是通過輸入命令執行操作,所以初學還很不適應。正如那句話說的windows能做的Linux都能做,windows不能 ...
  • 舊的小米6在抽屜吃灰半年,一直沒想好要怎麼處理,於是就想著安裝Linux。 完整教程來自https://blog.csdn.net/Greepex/article/details/85333027 原文里把每一個步驟都描述得很清楚(所以本文就不貼詳細步驟圖了,豎版截圖太影響觀感),但難免會踩一些坑。 ...
  • 常用的linux命令 ls 查看當前(或者指定)目錄下的文件列表 ls -l 查看詳細信息列表 ls -a 或ls -al 查看目錄下所有文件(包含隱藏文件)的詳細信息 cd ./ 切換到當前目錄 cd ../ 切換到上一級目錄 clear 清屏 (或者ctrl+l) / 根目錄 ~ 家目錄 cd ...
  • 打開dos命令視窗1、win+r-->運行-->cmd 2、摁住shift+滑鼠右擊 選擇 在此處打開命令視窗3、在磁碟某文件夾下,選擇標題欄中輸入框,輸入cmd 回車 windows下常用的命令 系統管理和文件管理systeminfo 獲取系統信息 系統 補丁 網卡path 查看環境變數set 查 ...
  • centOS 7安裝步驟: 1.選擇新建虛擬機,稍後安裝,linux選centos7 64位 2.位置改到存放虛擬機的文件夾 3.把硬碟空間改到40g,記憶體分到4g,1處理器2個核心 4 更改cd/dvd到鏡像位置。 5 選擇中文安裝 6選擇需要的安裝軟體,gui和gnome桌面 7 設置root密 ...
  • 背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 介紹 順著之前的分析,我們來到了 函數了,本以為一篇文章能搞定,大概掃了一遍代碼之後,我默默的把它拆 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...