主頁 > 軟體設計 > [Linux網路編程]執行緒池的封裝(結構體方式)

[Linux網路編程]執行緒池的封裝(結構體方式)

2021-04-25 11:21:08 軟體設計

執行緒池在實際的服務器開發是非常重要的一環,他涉及的概念也比較多,例如執行緒的使用,互斥鎖,條件變數,信號量的創建使用時機等等,同時你還要知道它如何自動銷毀和創建,實作一個較為智能的模式,
本文對執行緒池的一種構建方式進行詳細分解解讀注釋,但也有許多需要改進的地方,
完整的代碼文中已經給出,如需整個測驗專案,私信發,
目錄鏈接
在這里插入圖片描述

文章目錄

  • 1 為什么要epoll創建一個執行緒池
  • 2 執行緒池的實作流程
  • 3 準備作業,封裝互斥鎖和條件變數
  • 4 執行緒池的實作
    • 4.1 結構說明(重)
      • 4.1.1 任務結構體
      • 4.1.2 執行緒池結構體
      • 4.1.3 四個函式
    • 4.2 threadpool_add_task函式實作說明
      • 4.2.1 偽代碼(詳細中文說明)
      • 4.2.2 具體代碼實作
    • 4.3 thread_routine函式實作說明
      • 4.3.1 偽代碼(詳細中文說明)
      • 4.3.2 具體代碼實作
    • 4.4 threadpool_init函式實作說明
    • 4.5 threadpool_destroy函式實作說明
  • 5 測驗代碼
    • 5.1 main.c
    • 5.2 client.c
    • 5.3 測驗結果
  • 補充
  • 總結

1 為什么要epoll創建一個執行緒池

  1. 降低資源消耗,通過重復利用已創建的執行緒降低執行緒的創建和銷毀造成的消耗,
  2. 提高回應速度,當任務到達時,任務可以不需要等到執行緒創建就能立即執行,
  3. 提高執行緒的可管理性,執行緒為稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控,但是,要做到合理利用執行緒池,必須對其實作原理了如指掌,

2 執行緒池的實作流程

  1. 創建一個執行緒池,初始化其中屬性,可創建一定數量執行緒,放入佇列,或者不初始化
  2. 執行緒都處于阻塞等待狀態,不占用cpu,當超時等待,可以自己結束執行緒
  3. 當需要執行函式,則把函式包裝成任務,并把任務傳入空閑執行緒進行運行
  4. 當沒有空閑行程且小于最大執行緒數要求,則創建執行緒執行任務
  5. 執行完任務的執行緒不需要退出,阻塞等待即可(或者設定等待時間)
  6. 若有執行緒池銷毀通知,確保任務執行完退出銷毀

3 準備作業,封裝互斥鎖和條件變數

其實這邊的封裝就是為了方便使用,我們把信號量和互斥鎖封裝成一個結構體是有很大幫助的,
在實際使用中我們都知道實作互斥訪問(不懂可以參考生產者和消費者模型)其中一種方式就是條件變數和互斥鎖的組合使用,
多個執行緒可以理解成多個消費者,所以設計到很多共享資源,需要互斥鎖,而條件變數是為了方便通知執行緒有可以執行的任務了,

condition.h

#ifndef _CONDITION_H_
#define _CONDITION_H_

#include <pthread.h>

//結構體內放了一個鎖和條件變數
typedef struct condition
{
	pthread_mutex_t pmutex;
	pthread_cond_t pcond;
} condition_t;
//初始化鎖和條件變數
int condition_init(condition_t *cond);
//上鎖
int condition_lock(condition_t *cond);
//解鎖
int condition_unlock(condition_t *cond);
//阻塞等待喚醒
int condition_wait(condition_t *cond);
//設定時間等待
int condition_timedwait(condition_t *cond, const struct timespec *abstime);
//隨機喚醒一個阻塞的
int condition_signal(condition_t *cond);
//廣播
int condition_broadcast(condition_t *cond);
//銷毀條件變數和互斥鎖
int condition_destroy(condition_t *cond);

#endif /* _CONDITION_H_ */

# condition.c

#include "condition.h"  

int condition_init(condition_t *cond)
{
	int status;
	if ((status = pthread_mutex_init(&cond->pmutex, NULL)))
		return status;

	if ((status = pthread_cond_init(&cond->pcond, NULL)))
		return status;

	return 0;
}

int condition_lock(condition_t *cond)
{
	return pthread_mutex_lock(&cond->pmutex); 
}

int condition_unlock(condition_t *cond)
{
	return pthread_mutex_unlock(&cond->pmutex);
}

int condition_wait(condition_t *cond)
{
	return pthread_cond_wait(&cond->pcond, &cond->pmutex);
}

int condition_timedwait(condition_t *cond, const struct timespec *abstime)
{
	return pthread_cond_timedwait(&cond->pcond, &cond->pmutex, abstime);
}

int condition_signal(condition_t *cond)
{
	return pthread_cond_signal(&cond->pcond);
}

int condition_broadcast(condition_t* cond)
{
	return pthread_cond_broadcast(&cond->pcond);
}

int condition_destroy(condition_t* cond)
{
	int status;
	if ((status = pthread_mutex_destroy(&cond->pmutex)))
		return status;

	if ((status = pthread_cond_destroy(&cond->pcond)))
		return status;

	return 0;
}

4 執行緒池的實作

4.1 結構說明(重)

執行緒池主要組成就三個部分

  • task_t型別的結構體
  • threadpool_t型別結構體
  • 四個實作函式

4.1.1 任務結構體

任務結構體簡單的說就是對實際執行函式的封裝

特別注意一下第一個成員變數,其實就是函式指標,函式指標 的本質是一個指標,該指標的地址指向了一個函式,所以它是指向函式的指標,簡單的說:理解成一個未初始化的函式變數

把函式指標和引數分開作為引數其實就是為了在呼叫pthread_create方便傳入引數,第三個成員和鏈表的下一個指標域一個意思,我們這邊吧任務串成鏈表,

typedef struct task
{
	// 任務回呼函式
	// 簡單的說就是函式指標
	void *(*run)(void *arg);	
	// 回呼函式引數
	void *arg;					
	struct task *next;//指向的下一個結構體指標
} task_t;

4.1.2 執行緒池結構體

定義那么多關于執行緒的變數其實都是為了方便我們去管理執行緒,
唯一需要注意的是任務佇列的頭指標和尾指標,因為定義了這個,我們可以通過執行緒池物件成員很方便的放入或者取出任務,重要

typedef struct threadpool
{
	condition_t ready;		//初始化了一個條件變數和互斥鎖
	task_t *first;			//任務佇列頭指標
	task_t *last;			//任務佇列尾指標
	int counter;			//執行緒池中當前執行緒數
	int idle;				//執行緒池中當前正在等待任務的執行緒數
	int max_threads;		//執行緒池中最大允許的執行緒數
	int quit;				//銷毀執行緒池的時候置1
} threadpool_t;

4.1.3 四個函式

大致進行了一個介紹,具體解釋說明看后面,

// 初始化執行緒池
void threadpool_init(threadpool_t *pool, int threads);
// 往執行緒池中添加任務,同時喚醒執行緒(或者創造執行緒)去執行
void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg);
// 銷毀執行緒池
void threadpool_destroy(threadpool_t *pool);
//執行緒所執行的那個函式 (注意:并不是任務里面的那個函式)
//它的任務就是去判斷有沒有需要執行的任務,有的話就取出執行,否則等待or退出
void *thread_routine(void *arg);

4.2 threadpool_add_task函式實作說明

  • void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg);

作用
往執行緒池中添加任務(封裝的函式)(其實就是任務鏈表中添加任務,并且喚醒等待執行緒去執行 如果沒有空閑的執行緒并且小于限制的最大執行緒數 就去創建執行緒),這里的任務佇列的頭部指標和尾部指標在執行緒池都有定義 掛上去就對了

  • 引數一 執行緒池物件
  • 引數二 執行緒執行的具體函式
  • 引數二 具體函式的引數

對照偽代碼和具體代碼看

4.2.1 偽代碼(詳細中文說明)

void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg);
/*
{
malloc一個任務空間把傳入的執行的函式 和 引數掛上去
上鎖
if(如果頭指標為空)
{
 任務掛頭
}
   
else
{
    否則掛到尾巴的下一個   
    并把當前任務作為鏈表的最后一個
}
if(有空閑的執行緒)
{
    就去喚醒一個
}
else if(沒有空閑執行緒 并且存貨執行緒數不多于最大值)
{
    這部分有很大的改進空間,每次創建一個沒必要
    創建一個執行緒,執行thread_routine
    這邊的thread_routine就是自己去取任務執行
    執行緒數增加
}
解鎖
}
*/

4.2.2 具體代碼實作

// 往執行緒池中添加任務
void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg)
{
	// 生成新任務
	task_t *newtask = (task_t *)malloc(sizeof(task_t));
	newtask->run = run;
	newtask->arg = arg;
	newtask->next = NULL;

	condition_lock(&pool->ready);
	// 將任務添加到佇列
	if (pool->first == NULL)
		pool->first = newtask;
	else
		pool->last->next = newtask;
	pool->last = newtask;

	// 如果有等待執行緒,則喚醒其中一個
	if (pool->idle > 0)
		condition_signal(&pool->ready);
	else if (pool->counter < pool->max_threads)
	{
		// 沒有等待執行緒,并且當前執行緒數不超過最大執行緒數,則創建一個新執行緒
		pthread_t tid;
		pthread_create(&tid, NULL, thread_routine, pool);
		pool->counter++;
	}
	condition_unlock(&pool->ready);
}

4.3 thread_routine函式實作說明

  • void *thread_routine(void *arg);

作用
從鏈表取出(任務)(結構體)(函式)進行執行

  • 引數一 執行緒池物件

對照偽代碼和具體代碼看

4.3.1 偽代碼(詳細中文說明)

void *thread_routine(void *arg);
//執行緒呼叫的那個函式 
//作用就是:從鏈表取出(任務)(結構體)(函式)進行執行
//引數就是 傳入的執行緒物件 因為需要執行緒池物件的互斥鎖 條件變數 任務指標 這些東西
//具體 while(1)大回圈 
/*
while(1)
{
    上鎖
    while(如果沒有任務 并且執行緒池不要求銷毀)
    {
        設定時間等待
        超時退出,但是只是退出第一層,所以這邊還設定了timeout標志 準備二次退出

    }
    if(有任務)
    {
        我們就從任務頭指標去取任務,更改頭指標
        解鎖 
        釋放結構體空間 
        加鎖 (這邊加鎖 是應為下面有涉及執行緒數的加減操作)
    }
    if(沒有任務了 并且 執行緒池要求銷毀)
    {
        執行緒數--
        if(執行緒數==0)也就是說其他執行緒任務都結束了 那就
        {
            喚醒在等待摧毀的函式
            跳出回圈
        }
        釋放鎖
    }
    //這一個和第二個while是關聯的
    if(超時了就是從第二個while跳出了的 并且 依舊沒有任務了)
    {
        執行緒數減少
        釋放鎖
    }
}
*/

4.3.2 具體代碼實作

void *thread_routine(void *arg)
{
	struct timespec abstime;
	int timeout;
	printf("thread 0x%x is starting\n", (int)pthread_self());
	threadpool_t *pool = (threadpool_t *)arg;
	while (1)
	{
		timeout = 0;
		condition_lock(&pool->ready);
		pool->idle++;
		// 等待佇列有任務到來或者執行緒池銷毀通知
		while (pool->first == NULL && !pool->quit)
		{
			printf("thread 0x%x is waiting\n", (int)pthread_self());
			//condition_wait(&pool->ready);
			clock_gettime(CLOCK_REALTIME, &abstime);
			abstime.tv_sec += 2;
			int status = condition_timedwait(&pool->ready, &abstime);
			if (status == ETIMEDOUT)
			{
				printf("thread 0x%x is wait timed out\n", (int)pthread_self());
				timeout = 1;
				break;
			}
		}

		// 等待到條件,處于作業狀態
		pool->idle--;

		// 等待到任務
		if (pool->first != NULL)
		{
			// 從隊頭取出任務
			task_t *t = pool->first;
			pool->first = t->next;
			// 執行任務需要一定的時間,所以要先解鎖,以便生產者行程
			// 能夠往佇列中添加任務,其它消費者執行緒能夠進入等待任務
			condition_unlock(&pool->ready);
			t->run(t->arg);
			free(t);
			condition_lock(&pool->ready);
		}
		// 如果等待到執行緒池銷毀通知, 且任務都執行完畢
		if (pool->quit && pool->first == NULL)
		{
			pool->counter--;
			if (pool->counter == 0)
				condition_signal(&pool->ready);

			condition_unlock(&pool->ready);
			// 跳出回圈之前要記得解鎖
			break;
		}

		if (timeout && pool->first == NULL)
		{
			pool->counter--;
			condition_unlock(&pool->ready);
			// 跳出回圈之前要記得解鎖
			break;
		}
		condition_unlock(&pool->ready);
	}

	printf("thread 0x%x is exting\n", (int)pthread_self());
	return NULL;

}

一開始弄錯的是執行緒執行的函式和實際我們要執行的函式不一樣,可以理解成一個嵌套,

4.4 threadpool_init函式實作說明

void threadpool_init(threadpool_t *pool, int threads)
{
	// 對執行緒池中的各個欄位初始化
	condition_init(&pool->ready);
	pool->first = NULL;
	pool->last = NULL;
	pool->counter = 0;
	pool->idle = 0;
	pool->max_threads = threads;
	pool->quit = 0;
}

4.5 threadpool_destroy函式實作說明

簡單的說就是廣播通知所有執行緒執行完任務,然后執行緒退出

// 銷毀執行緒池
void threadpool_destroy(threadpool_t *pool)
{
	if (pool->quit)
	{
		return;
	}
	condition_lock(&pool->ready);
	pool->quit = 1;
	if (pool->counter > 0)
	{
		if (pool->idle > 0)
			condition_broadcast(&pool->ready);

		// 處于執行任務狀態中的執行緒,不會收到廣播
		// 執行緒池需要等待執行任務狀態中的執行緒全部退出

		while (pool->counter > 0)
			condition_wait(&pool->ready);
	}
	condition_unlock(&pool->ready);
	condition_destroy(&pool->ready);
}

5 測驗代碼

這邊通信方式用的是共享記憶體,設計了一個簡單的結構體,test 函式是我們具體要做的事,這邊做的就是簡單的列印而已,實際上我們可能對客戶端發送來的資訊做更多的處理,

5.1 main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <signal.h>
#include <pthread.h>
#include "threadpool.h"

#define PORT 8000
#define OPEN_MAX 1024

struct shared{
	int written;    // 作為一個標志,非0:表示可讀,0:表示可寫
	char text[OPEN_MAX];      // 記錄寫入 和 讀取 的文本

};
pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_t pid[5];


void *Test(void* arg)
{
    struct shared* shm_shared = (struct shared*)arg; 
		while(1)
		{
			if(shm_shared->written == 1)
			{
        		printf("read: %s\n",shm_shared->text);
			//	write(5,shm_shared->text,);
       
				shm_shared->written = 0;
				break;
           	}	
		}
	return NULL;
}


int main()
{
	int i;
	
	struct shared *shm_shared;
   
   //創建一個執行緒池
   	threadpool_t pool;
	threadpool_init(&pool, 3);
	
	//創建共享記憶體
	int shmid;
	void* shmadd;
	//struct shared *shm_shared;
	if((shmid = shmget(IPC_PRIVATE,10,IPC_CREAT|0666)) < 0)
	{
		perror("shmget\n");
		exit(-1);
	}
	printf("創建的共享記憶體為:%d\n",shmid);

	//掛載共享記憶體到行程內,成功回傳共享記憶體的起始地址
	if((shmadd = shmat(shmid,NULL,0)) < (char*)0)
	{
		perror("shmat");
		exit(-1);
	}

	shm_shared = (struct shared*)shmadd;
	shm_shared->written = 0;

	//創建套接字,監聽檔案描述符
	int sockfd;
	struct sockaddr_in seraddr;	
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//設定非阻塞
	int flags = fcntl(sockfd,F_GETFL);
	fcntl(sockfd,F_SETFL,flags | O_NONBLOCK);
	//初始化埠和IP
	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	seraddr.sin_port = htons(PORT);

	int ret;
	int on;
	ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(const char*)&on,sizeof(on));
	if (ret == -1)
		perror("bind");
	//系結服務器
	ret = bind(sockfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
	if(ret == -1){
		perror("bind");
	}

	//監聽套接字
	if(listen(sockfd,SOMAXCONN) < 0)
	{
		perror("listen");
	}

	int client[OPEN_MAX];
	int efd;
	struct epoll_event event,events[OPEN_MAX];
	//將客戶端標識初始化為-1
	for(i = 0; i<OPEN_MAX; i++)
	{
		client[i] = -1;
	}
	
	//監聽事件個數
	efd = epoll_create(OPEN_MAX);
	if(efd == -1)
	{
		perror("epoll_create");
	}
		
	event.events = EPOLLIN;   //監聽檔案描述符的可讀事件
	event.data.fd = sockfd;     //設定監聽的檔案描述符

	ret = epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&event);
	if(ret == -1)
	{
		perror("epoll_ctl");
	} 

	socklen_t len;
	int confd,nready;
	struct sockaddr_in cliaddr;
	char  buf[OPEN_MAX] = {0};
	while(1)
	{
		printf("wait....\n");
		nready = epoll_wait(efd,events,OPEN_MAX,-1);
		for(i = 0; i<nready;i++)
		{
			if(events[i].data.fd == sockfd)
			{
				len = sizeof(cliaddr);
				confd = accept(sockfd,(struct sockaddr*)&cliaddr,&len);
				printf("ip = %s;port = %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
				
				event.data.fd = confd;
				event.events = EPOLLIN | EPOLLET;
				epoll_ctl(efd,EPOLL_CTL_ADD,confd,&event);

				//設定為非阻塞模式
				//flags = fcntl(confd,F_GETFL);
				//fcntl(confd, F_SETFL, flags | O_NONBLOCK);
			}
			else
			{
				confd = events[i].data.fd;
				printf("connfd=%d\n",confd);
		
				
			
					int nread = read(confd,buf,sizeof(buf));
			
					if(nread == 0)
					{
						close(confd);
						printf("client close\n");
						event = events[i];
						epoll_ctl(efd,EPOLL_CTL_DEL,confd,&event);
						//break;
					}			
				 	else{
						
						//將資料寫入共享記憶體中
					
						strcpy(shm_shared->text,buf);
						shm_shared->written = 1;
					
						//添加一個任務
						threadpool_add_task(&pool, Test, shm_shared);
						memset(buf,0x0,OPEN_MAX);
						
					}
				
			}
		}	
	}


	
	//釋放共享記憶體
	shmdt(shmadd);
	close(sockfd);
	close(efd);
	return 0;
}

5.2 client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#define SERV_PORT 8000
#define OPEN_MAX 1024


int main(int arg,char* argv[])
{
	struct sockaddr_in addr;
	int sockfd ,id;

	sockfd = socket(AF_INET,SOCK_STREAM,0);

	bzero(&addr,sizeof(addr));
	addr.sin_family = AF_INET;
	inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
	addr.sin_port = htons(SERV_PORT);

	//連接服務器
	connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));


	char wbuf[OPEN_MAX] = {0};
	char rbuf[OPEN_MAX] = {0};

	while(fgets(wbuf,sizeof(wbuf),stdin) != NULL)
	{
		//通過sockfd給服務器發送資料
		write(sockfd,wbuf,strlen(wbuf));
		
		/*
		id = read(sockfd,rbuf,sizeof(rbuf));
		if(id == 0)
		{
			printf("the other side has been closed\n");
		}
		*/
		fputs(rbuf,stdout);
		memset(wbuf,0,1024);
		memset(rbuf,0,1024);
	}
	close(sockfd);
	return 0;
}

5.3 測驗結果

在這里插入圖片描述
在這里插入圖片描述

補充

  • 一個容易弄錯的就是實際執行的函式和執行緒執行的函式是不同的,二者是嵌套關系,

總結

如有錯誤,歡迎指出,

在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/279843.html

標籤:其他

上一篇:Java程式員需要什么學歷?作業如何選擇?未來咋樣?

下一篇:OpenCV呼叫海康威視等攝像頭(處理rtsp視頻流)方法以及,出現記憶體溢位(error while decoding)或者高延遲問題解決

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more