自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

協(xié)程庫(kù)Libtask源碼分析之架構(gòu)篇

開(kāi)發(fā) 架構(gòu)
本文介紹libtask的基礎(chǔ)原理。我們從libtask的main函數(shù)開(kāi)始,這個(gè)main函數(shù)就是我們?cè)赾語(yǔ)言中使用的c函數(shù),libtask本身實(shí)現(xiàn)了main這個(gè)函數(shù),用戶使用libtask時(shí),要實(shí)現(xiàn)的是taskmain函數(shù)。taskmain和main的函數(shù)聲明是一樣的。下面我們看一下main函數(shù)。

 [[382125]]

本文轉(zhuǎn)載自微信公眾號(hào)「編程雜技」,作者theanarkh 。轉(zhuǎn)載本文請(qǐng)聯(lián)系編程雜技公眾號(hào)。

前言:假設(shè)讀者已經(jīng)了解了協(xié)程的概念,實(shí)現(xiàn)協(xié)程的底層技術(shù)支持。本文會(huì)介紹基于底層基礎(chǔ),如何實(shí)現(xiàn)協(xié)程以及協(xié)程的應(yīng)用(更多基礎(chǔ)可以點(diǎn)擊這里[1])。

libtask是google大佬Russ Cox(Go的核心開(kāi)發(fā)者)所寫(xiě),本文介紹libtask的基礎(chǔ)原理。我們從libtask的main函數(shù)開(kāi)始,這個(gè)main函數(shù)就是我們?cè)赾語(yǔ)言中使用的c函數(shù),libtask本身實(shí)現(xiàn)了main這個(gè)函數(shù),用戶使用libtask時(shí),要實(shí)現(xiàn)的是taskmain函數(shù)。taskmain和main的函數(shù)聲明是一樣的。下面我們看一下main函數(shù)。

  1. int main(int argc, char **argv) 
  2.     struct sigaction sa, osa; 
  3.     // 注冊(cè)SIGQUIT信號(hào)處理函數(shù) 
  4.     memset(&sa, 0, sizeof sa); 
  5.     sa.sa_handler = taskinfo; 
  6.     sa.sa_flags = SA_RESTART; 
  7.     sigaction(SIGQUIT, &sa, &osa); 
  8.  
  9.     // 保存命令行參數(shù) 
  10.     argv0 = argv[0]; 
  11.     taskargc = argc; 
  12.     taskargv = argv; 
  13.  
  14.     if(mainstacksize == 0) 
  15.         mainstacksize = 256*1024; 
  16.     // 創(chuàng)建第一個(gè)協(xié)程 
  17.     taskcreate(taskmainstart, nil, mainstacksize); 
  18.     // 開(kāi)始調(diào)度 
  19.     taskscheduler(); 
  20.     fprint(2, "taskscheduler returned in main!\n"); 
  21.     abort(); 
  22.     return 0; 

main函數(shù)主要的兩個(gè)邏輯是taskcreate和taskscheduler函數(shù)。我們先來(lái)看taskcreate。

  1. int taskcreate(void (*fn)(void*), void *arg, uint stack) 
  2.     int id; 
  3.     Task *t; 
  4.  
  5.     t = taskalloc(fn, arg, stack); 
  6.     taskcount++; 
  7.     id = t->id; 
  8.     // 記錄位置 
  9.     t->alltaskslot = nalltask; 
  10.     // 保存到alltask中 
  11.     alltask[nalltask++] = t; 
  12.     // 修改狀態(tài)為就緒,可以被調(diào)度,并且加入到就緒隊(duì)列 
  13.     taskready(t); 
  14.     return id; 

taskcreate首先調(diào)用taskalloc分配一個(gè)表示協(xié)程的結(jié)構(gòu)體Task。我們看看這個(gè)結(jié)構(gòu)體的定義。

  1. struct Task 
  2.     char    name[256];    // offset known to acid 
  3.     char    state[256]; 
  4.     // 前后指針 
  5.     Task    *next
  6.     Task    *prev; 
  7.     Task    *allnext; 
  8.     Task    *allprev; 
  9.     // 執(zhí)行上下文 
  10.     Context    context; 
  11.     // 睡眠時(shí)間 
  12.     uvlong    alarmtime; 
  13.     uint    id; 
  14.     // 棧信息 
  15.     uchar    *stk; 
  16.     uint    stksize; 
  17.     //是否退出了 
  18.     int    exiting; 
  19.     // 在alltask的索引 
  20.     int    alltaskslot; 
  21.     // 是否是系統(tǒng)協(xié)程 
  22.     int    system; 
  23.     // 是否就緒狀態(tài) 
  24.     int    ready; 
  25.     // 入口函數(shù) 
  26.     void    (*startfn)(void*); 
  27.     // 入口參數(shù) 
  28.     void    *startarg; 
  29.     // 自定義數(shù)據(jù) 
  30.     void    *udata; 
  31. }; 

接著看看taskalloc的實(shí)現(xiàn)。

  1. // 分配一個(gè)協(xié)程所需要的內(nèi)存和初始化某些字段 
  2. static Task* 
  3. taskalloc(void (*fn)(void*), void *arg, uint stack) 
  4.     Task *t; 
  5.     sigset_t zero; 
  6.     uint x, y; 
  7.     ulong z; 
  8.  
  9.     /* allocate the task and stack together */ 
  10.     // 結(jié)構(gòu)體本身的大小+棧大小 
  11.     t = malloc(sizeof *t+stack); 
  12.     memset(t, 0, sizeof *t); 
  13.     // 棧的內(nèi)存位置 
  14.     t->stk = (uchar*)(t+1); 
  15.     // 棧大小 
  16.     t->stksize = stack; 
  17.     // 協(xié)程id 
  18.     t->id = ++taskidgen; 
  19.     // 協(xié)程工作函數(shù)和參數(shù) 
  20.     t->startfn = fn; 
  21.     t->startarg = arg; 
  22.  
  23.     /* do a reasonable initialization */ 
  24.     memset(&t->context.uc, 0, sizeof t->context.uc); 
  25.     sigemptyset(&zero); 
  26.     // 初始化uc_sigmask字段為空,即不阻塞信號(hào) 
  27.     sigprocmask(SIG_BLOCK, &zero, &t->context.uc.uc_sigmask); 
  28.  
  29.     /* must initialize with current context */ 
  30.     // 初始化uc字段 
  31.     getcontext(&t->context.uc)  
  32.     // 設(shè)置協(xié)程執(zhí)行時(shí)的棧位置和大小 
  33.     t->context.uc.uc_stack.ss_sp = t->stk+8; 
  34.     t->context.uc.uc_stack.ss_size = t->stksize-64; 
  35.     z = (ulong)t; 
  36.     y = z; 
  37.     z >>= 16;    /* hide undefined 32-bit shift from 32-bit compilers */ 
  38.     x = z>>16; 
  39.     // 保存信息到uc字段 
  40.     makecontext(&t->context.uc, (void(*)())taskstart, 2, y, x); 
  41.  
  42.     return t; 

taskalloc函數(shù)代碼看起來(lái)很多,但是邏輯不算復(fù)雜,就是申請(qǐng)Task結(jié)構(gòu)體所需的內(nèi)存和執(zhí)行時(shí)棧的內(nèi)存,然后初始化各個(gè)字段。這樣,一個(gè)協(xié)程就誕生了。接著執(zhí)行taskready把協(xié)程加入就緒隊(duì)列。

  1. // 修改協(xié)程的狀態(tài)為就緒并加入就緒隊(duì)列 
  2. void taskready(Task *t) 
  3.     t->ready = 1; 
  4.     addtask(&taskrunqueue, t); 
  5.  
  6. // 把協(xié)程插入隊(duì)列中,如果之前在其他隊(duì)列,則會(huì)被移除 
  7. void addtask(Tasklist *l, Task *t) 
  8.     if(l->tail){ 
  9.         l->tail->next = t; 
  10.         t->prev = l->tail; 
  11.     }else
  12.         l->head = t; 
  13.         t->prev = nil; 
  14.     } 
  15.     l->tail = t; 
  16.     t->next = nil; 

taskrunqueue記錄了所有就緒的協(xié)程。創(chuàng)建了協(xié)程并加入隊(duì)列后,協(xié)程還沒(méi)有開(kāi)始執(zhí)行,就像操作系統(tǒng)的進(jìn)程和線程一樣,需要有一個(gè)調(diào)度器來(lái)調(diào)度執(zhí)行。下面我們看看調(diào)度器的實(shí)現(xiàn)。

  1. // 協(xié)程調(diào)度中心 
  2. static void taskscheduler(void) 
  3.     int i; 
  4.     Task *t; 
  5.     for(;;){ 
  6.         // 沒(méi)有用戶協(xié)程了,則退出 
  7.         if(taskcount == 0) 
  8.             exit(taskexitval); 
  9.         // 從就緒隊(duì)列拿出一個(gè)協(xié)程 
  10.         t = taskrunqueue.head; 
  11.         if(t == nil){ 
  12.             fprint(2, "no runnable tasks! %d tasks stalled\n", taskcount); 
  13.             exit(1); 
  14.         } 
  15.         // 從就緒隊(duì)列刪除該協(xié)程 
  16.         deltask(&taskrunqueue, t); 
  17.         t->ready = 0; 
  18.         // 保存正在執(zhí)行的協(xié)程 
  19.         taskrunning = t; 
  20.         // 切換次數(shù)加一 
  21.         tasknswitch++; 
  22.         // 切換到t執(zhí)行,并且保存當(dāng)前上下文到taskschedcontext(即下面要執(zhí)行的代碼) 
  23.         contextswitch(&taskschedcontext, &t->context); 
  24.         // 執(zhí)行到這說(shuō)明沒(méi)有協(xié)程在執(zhí)行(t切換回來(lái)的),置空 
  25.         taskrunning = nil; 
  26.         // 剛才執(zhí)行的協(xié)程t退出了 
  27.         if(t->exiting){ 
  28.             // 不是系統(tǒng)協(xié)程,則個(gè)數(shù)減一 
  29.             if(!t->system) 
  30.                 taskcount--; 
  31.             // 當(dāng)前協(xié)程在alltask的索引 
  32.             i = t->alltaskslot; 
  33.             // 把最后一個(gè)協(xié)程換到當(dāng)前協(xié)程的位置,因?yàn)樗顺隽?nbsp;
  34.             alltask[i] = alltask[--nalltask]; 
  35.             // 更新被置換協(xié)程的索引 
  36.             alltask[i]->alltaskslot = i; 
  37.             // 釋放堆內(nèi)存 
  38.             free(t); 
  39.         } 
  40.     } 

調(diào)度器的代碼看起來(lái)很多,但是核心邏輯就三個(gè) 1 從就緒隊(duì)列中拿出一個(gè)協(xié)程t,并把t移出就緒隊(duì)列 2 通過(guò)contextswitch切換到協(xié)程t中執(zhí)行 3 協(xié)程t切換回調(diào)度中心,如果t已經(jīng)退出,則修改數(shù)據(jù)結(jié)構(gòu),然后回收他占據(jù)的內(nèi)存。如果t沒(méi)退出,則繼續(xù)調(diào)度其他協(xié)程執(zhí)行。至此,協(xié)程就開(kāi)始跑起來(lái)了。并且也有了調(diào)度系統(tǒng)。這里的調(diào)度機(jī)制是比較簡(jiǎn)單的,就是按著先進(jìn)先出的方式就緒調(diào)度,并且是非搶占的。即沒(méi)有按時(shí)間片調(diào)度的概念,一個(gè)協(xié)程的執(zhí)行時(shí)間由自己決定,放棄執(zhí)行的權(quán)力也是自己控制的,當(dāng)協(xié)程不想執(zhí)行了可以調(diào)用taskyield讓出cpu。

  1. // 協(xié)程主動(dòng)讓出cpu 
  2. int taskyield(void) 
  3.     int n; 
  4.     // 當(dāng)前切換協(xié)程的次數(shù) 
  5.     n = tasknswitch; 
  6.     // 插入就緒隊(duì)列,等待后續(xù)的調(diào)度 
  7.     taskready(taskrunning); 
  8.     taskstate("yield"); 
  9.     // 切換協(xié)程 
  10.     taskswitch(); 
  11.     // 等于0說(shuō)明當(dāng)前只有自己一個(gè)協(xié)程,調(diào)度的時(shí)候tasknswitch加一,所以這里減一 
  12.     return tasknswitch - n - 1; 
  13.  
  14. /* 
  15.     切換協(xié)程,taskrunning是正在執(zhí)行的協(xié)程,taskschedcontext是調(diào)度協(xié)程(主線程)的上下文, 
  16.     切換到調(diào)度中心,并保持當(dāng)前上下文到taskrunning->context 
  17. */ 
  18. void taskswitch(void) 
  19.     needstack(0); 
  20.     contextswitch(&taskrunning->context, &taskschedcontext); 
  21.  
  22. // 真正切換協(xié)程的邏輯 
  23. static void contextswitch(Context *from, Context *to
  24.     if(swapcontext(&from->uc, &to->uc) < 0){ 
  25.         fprint(2, "swapcontext failed: %r\n"); 
  26.         assert(0); 
  27.     } 

yield的邏輯也很簡(jiǎn)單,因?yàn)閰f(xié)程在執(zhí)行的時(shí)候,是不處于就緒隊(duì)列的,當(dāng)協(xié)程準(zhǔn)備讓出cpu時(shí),協(xié)程首先把自己重新加入到就緒隊(duì)列,等待下次被調(diào)度執(zhí)行。當(dāng)然我們也可以直接調(diào)度contextswitch切換到其他協(xié)程。重點(diǎn)在于什么時(shí)候應(yīng)該讓出cpu,又什么時(shí)候應(yīng)該被調(diào)度執(zhí)行。接下來(lái)會(huì)詳細(xì)講解。至此,我們已經(jīng)有了支持協(xié)程所需要的底層基礎(chǔ)。我們看到這個(gè)實(shí)現(xiàn)的思路也不是很復(fù)雜,首先有一個(gè)隊(duì)列表示待執(zhí)行的的協(xié)程,每一個(gè)協(xié)程對(duì)應(yīng)一個(gè)Task結(jié)構(gòu)體。然后調(diào)度中心不斷地按照先進(jìn)先出的方式去調(diào)度協(xié)程的執(zhí)行就可以。因?yàn)闆](méi)有搶占機(jī)制,所以調(diào)度中心是依賴協(xié)程本身去驅(qū)動(dòng)的,協(xié)程需要主動(dòng)讓出cpu,把上下文切換回調(diào)度中心,調(diào)度中心才能進(jìn)行下一輪的調(diào)度。接下來(lái)我們看看,基于這些底層基礎(chǔ),如果實(shí)現(xiàn)一個(gè)基于協(xié)程的服務(wù)器。下面我們通過(guò)一個(gè)例子進(jìn)行講解。

  1. void 
  2. taskmain(int argc, char **argv) 
  3.     // 啟動(dòng)一個(gè)tcp服務(wù)器 
  4.     if((fd = netannounce(TCP, 0, atoi(argv[1]))) < 0){ 
  5.         // ... 
  6.     } 
  7.     // 改為非阻塞模式 
  8.     fdnoblock(fd); 
  9.     // accept成功后創(chuàng)建一個(gè)客戶端協(xié)程 
  10.     while((cfd = netaccept(fd, remote, &rport)) >= 0){ 
  11.         taskcreate(proxytask, (void*)cfd, STACK); 
  12.     } 

我們剛才講過(guò)taskmain是我們需要實(shí)現(xiàn)的函數(shù),首先通過(guò)netannounce建立一個(gè)tcp服務(wù)器。接著把fd改成非阻塞的,這個(gè)非常重要,因?yàn)樵诤竺嬲{(diào)用accept的時(shí)候,如果是阻塞的文件描述符,那么就會(huì)引起進(jìn)程掛起,而非阻塞模式下,操作系統(tǒng)會(huì)返回EAGAIN的錯(cuò)誤碼,通過(guò)這個(gè)錯(cuò)誤碼我們可以決定下一步做什么。我們看看netaccept的實(shí)現(xiàn)。

  1. // 處理(摘下)連接 
  2. int 
  3. netaccept(int fd, char *server, int *port) 
  4.     int cfd, one; 
  5.     struct sockaddr_in sa; 
  6.     uchar *ip; 
  7.     socklen_t len; 
  8.     // 注冊(cè)事件到epoll,等待事件觸發(fā) 
  9.     fdwait(fd, 'r'); 
  10.     len = sizeof sa; 
  11.     // 觸發(fā)后說(shuō)明有連接了,則執(zhí)行accept 
  12.     if((cfd = accept(fd, (void*)&sa, &len)) < 0){ 
  13.         return -1; 
  14.     } 
  15.     // 和客戶端通信的fd也改成非阻塞模式 
  16.     fdnoblock(cfd); 
  17.     one = 1; 
  18.     setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof one); 
  19.     return cfd; 

netaccept就是通過(guò)調(diào)用accept逐個(gè)處理tcp連接,但是在accept之前,有一個(gè)非常重要的操作fdwait。

  1. // 協(xié)程因?yàn)榈却齣o需要切換 
  2. void fdwait(int fd, int rw) 
  3. {     
  4.     // 是否已經(jīng)初始化epoll 
  5.     if(!startedfdtask){ 
  6.         startedfdtask = 1; 
  7.         epfd = epoll_create(1); 
  8.         // 沒(méi)有初始化則創(chuàng)建一個(gè)協(xié)程,做io管理 
  9.         taskcreate(fdtask, 0, 32768); 
  10.     } 
  11.     struct epoll_event ev = {0}; 
  12.     // 記錄事件對(duì)應(yīng)的協(xié)程和感興趣的事件 
  13.     ev.data.ptr = taskrunning; 
  14.     switch(rw){ 
  15.     case 'r'
  16.         ev.events |= EPOLLIN | EPOLLPRI; 
  17.         break; 
  18.     case 'w'
  19.         ev.events |= EPOLLOUT; 
  20.         break; 
  21.     } 
  22.  
  23.     int r = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); 
  24.     // 切換到其他協(xié)程,等待被喚醒 
  25.     taskswitch(); 
  26.     // 喚醒后函數(shù)剛才注冊(cè)的事件 
  27.     epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev); 

fdwait首先把fd注冊(cè)到epoll中,然后把協(xié)程切換到下一個(gè)待執(zhí)行的協(xié)程。這里有個(gè)細(xì)節(jié),當(dāng)協(xié)程X被調(diào)度執(zhí)行的時(shí)候,他是脫離了就緒隊(duì)列的,而taskswitch函數(shù)只是實(shí)現(xiàn)了切換上下文到調(diào)度中心,調(diào)度中心會(huì)從就緒隊(duì)列從選擇下一個(gè)協(xié)程執(zhí)行,那么這時(shí)候,脫離就緒隊(duì)列的協(xié)程X就處于孤島狀態(tài),看起來(lái)再也無(wú)法給調(diào)度中心選中執(zhí)行,這個(gè)問(wèn)題的處理方式是,把協(xié)程、fd和感興趣的事件信息一起注冊(cè)到epoll中,當(dāng)epoll監(jiān)聽(tīng)到某個(gè)fd的事件發(fā)生時(shí),就會(huì)把對(duì)應(yīng)的協(xié)程加入就緒隊(duì)列,這樣協(xié)程就可以被調(diào)度執(zhí)行了。在fdwait函數(shù)一開(kāi)始那里處理了epoll相關(guān)的邏輯。epoll的邏輯也是在一個(gè)協(xié)程中執(zhí)行的,但是epoll所在協(xié)程和一般協(xié)程不一樣,類似于操作系統(tǒng)的內(nèi)核線程一樣,epoll所在的協(xié)程成為系統(tǒng)協(xié)程,即不是用戶定義的,而是系統(tǒng)定義的。我們看一下實(shí)現(xiàn)

  1. void fdtask(void *v) 
  2.     int i, ms; 
  3.     Task *t; 
  4.     uvlong now; 
  5.     // 變成系統(tǒng)協(xié)程 
  6.     tasksystem(); 
  7.     struct epoll_event events[1000]; 
  8.     for(;;){ 
  9.         /* let everyone else run */ 
  10.         // 大于0說(shuō)明還有其他就緒協(xié)程可執(zhí)行,則先讓給他們執(zhí)行,否則往下執(zhí)行 
  11.         while(taskyield() > 0) 
  12.             ; 
  13.         /* we're the only one runnable - poll for i/o */ 
  14.         errno = 0; 
  15.         // 沒(méi)有定時(shí)事件則一直阻塞 
  16.         if((t=sleeping.head) == nil) 
  17.             ms = -1; 
  18.         else
  19.             /* sleep at most 5s */ 
  20.             now = nsec(); 
  21.             if(now >= t->alarmtime) 
  22.                 ms = 0; 
  23.             else if(now+5*1000*1000*1000LL >= t->alarmtime) 
  24.                 ms = (t->alarmtime - now)/1000000; 
  25.             else 
  26.                 ms = 5000; 
  27.         } 
  28.         int nevents; 
  29.         // 等待事件發(fā)生,ms是等待的超時(shí)時(shí)間 
  30.         if((nevents = epoll_wait(epfd, events, 1000, ms)) < 0){ 
  31.             if(errno == EINTR) 
  32.                 continue
  33.             fprint(2, "epoll: %s\n", strerror(errno)); 
  34.             taskexitall(0); 
  35.         } 
  36.  
  37.         /* wake up the guys who deserve it */ 
  38.         // 事件觸發(fā),把對(duì)應(yīng)協(xié)程插入就緒隊(duì)列 
  39.         for(i=0; i<nevents; i++){ 
  40.             taskready((Task *)events[i].data.ptr); 
  41.         } 
  42.  
  43.         now = nsec(); 
  44.         // 處理超時(shí)事件 
  45.         while((t=sleeping.head) && now >= t->alarmtime){ 
  46.             deltask(&sleeping, t); 
  47.             if(!t->system && --sleepingcounted == 0) 
  48.                 taskcount--; 
  49.             taskready(t); 
  50.         } 
  51.     } 

我們看到epoll的處理邏輯和一般服務(wù)器的類似,通過(guò)epoll_wait阻塞,然后epoll_wait返回時(shí),處理每一個(gè)發(fā)生的事件,而且libtask還支持超時(shí)事件。另外libtask中當(dāng)還有其他就緒協(xié)程的時(shí)候,是不會(huì)進(jìn)入epoll_wait的,它會(huì)把cpu讓給就緒的協(xié)程(通過(guò)taskyield函數(shù)),當(dāng)就緒隊(duì)列只有epoll所在的協(xié)程時(shí)才會(huì)進(jìn)入epoll的邏輯。至此,我們看到了libtask中如何把異步變成同步的。當(dāng)用戶要調(diào)用一個(gè)可能會(huì)引起進(jìn)程掛起的接口時(shí),就可以調(diào)用libtask提供的一個(gè)相應(yīng)的API,比如我們想讀一個(gè)文件,我們可以調(diào)用libtask的fdread。

  1. int 
  2. fdread(int fd, void *buf, int n) 
  3.     int m; 
  4.     // 非阻塞讀,如果不滿足則再注冊(cè)到epoll,參考fdread1 
  5.     while((m=read(fd, buf, n)) < 0 && errno == EAGAIN) 
  6.         fdwait(fd, 'r'); 
  7.     return m; 

這樣就不需要擔(dān)心進(jìn)程被掛起,同時(shí)也不需要處理epoll相關(guān)的邏輯(注冊(cè)事件,事件觸發(fā)時(shí)的處理等等)。異步轉(zhuǎn)同步,libtask的方式就是通過(guò)提供對(duì)應(yīng)的API,先把用戶的fd注冊(cè)到epoll中,然后切換到其他協(xié)程,等epoll監(jiān)聽(tīng)到事件觸發(fā)時(shí),就會(huì)把對(duì)應(yīng)的協(xié)程插入就緒隊(duì)列,當(dāng)該協(xié)程被調(diào)度中心選中執(zhí)行時(shí),就會(huì)繼續(xù)執(zhí)行剩下的邏輯而不會(huì)引起進(jìn)程掛起,因?yàn)檫@時(shí)候所等待的條件已經(jīng)滿足。

總結(jié):libtask的設(shè)計(jì)思想就是把業(yè)務(wù)邏輯封裝到一個(gè)個(gè)協(xié)程中,由libtask實(shí)現(xiàn)協(xié)程的調(diào)度,在各個(gè)業(yè)務(wù)邏輯中進(jìn)行切換,從而驅(qū)動(dòng)著系統(tǒng)的運(yùn)行。另外libtask也提供了一個(gè)網(wǎng)絡(luò)和文件io異步變同步的解決方案。使得我們使用起來(lái)更加方便,高效。今天先講到這里。

References

[1] 更多基礎(chǔ)可以點(diǎn)擊這里: https://github.com/theanarkh/read-libtask-code/blob/main/README.md

 

責(zé)任編輯:武曉燕 來(lái)源: 編程雜技
相關(guān)推薦

2021-02-20 06:09:46

libtask協(xié)程鎖機(jī)制

2025-02-08 09:13:40

2023-04-19 21:20:49

Tars-Cpp協(xié)程

2021-05-20 09:14:09

Kotlin協(xié)程掛起和恢復(fù)

2021-09-16 09:59:13

PythonJavaScript代碼

2021-08-04 16:19:55

AndroidKotin協(xié)程Coroutines

2022-09-12 06:35:00

C++協(xié)程協(xié)程狀態(tài)

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2023-07-13 08:06:05

應(yīng)用協(xié)程阻塞

2023-12-05 13:46:09

解密協(xié)程線程隊(duì)列

2021-05-21 08:21:57

Go語(yǔ)言基礎(chǔ)技術(shù)

2021-12-09 06:41:56

Python協(xié)程多并發(fā)

2023-10-24 19:37:34

協(xié)程Java

2014-02-11 09:28:57

2016-10-28 17:39:47

phpgolangcoroutine

2017-05-02 11:38:00

PHP協(xié)程實(shí)現(xiàn)過(guò)程

2020-11-29 17:03:08

進(jìn)程線程協(xié)程

2023-08-08 07:18:17

協(xié)程管道函數(shù)

2023-11-04 20:00:02

C++20協(xié)程

2024-02-05 09:06:25

Python協(xié)程Asyncio庫(kù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)