1. 程式人生 > >利用ucontext.h中介面實現使用者級別執行緒庫

利用ucontext.h中介面實現使用者級別執行緒庫

想必大家在初次接觸多執行緒或多程序時一定會感覺這是多麼的神奇!一個程式居然可以“同時”執行好多工作,此時你一定有一探其究竟的想法吧?可是無奈,這些都是核心中十分複雜的程式碼。對於初入計算機大門的我們,去看那些東西,的確有些困難。但是不用灰心,本篇博文我將為大家介紹一個由我自己實現的簡單執行緒,程式碼也就100多行,程式還很簡陋,但是我感覺這有助於我們對程序或執行緒的理解

1.設計思路

要實現執行緒,那麼就先得有個簡單的設計思路,具體有如下幾步

.實現能夠儲存當前上下文的功能函式,並實現能夠將當前程式執行切向某個函式的功能函式。如果自己要做到這點,一定會很麻煩,也不簡
單,好在ucontext.h標頭檔案中為我們提供了該類功能函式的API
.如何進行執行緒排程?我採用了傳統的時間片輪訓,設定一個定時器,當定時器觸發時,將會觸發訊號處
理函式(即排程函式)

我想能夠實現上述倆點,那麼實現執行緒這項工作剩下的也都是些細枝末葉了

2.前期知識準備

要完成1中的第一點,我們只需要學習一下ucontext.h為我們提供的幾個API介面的使用即可
具體如下

1.獲取當前執行程式上下文的API

int getcontext(ucontext_t *ucp);
//失敗返回-1

ucontext_t結構體定義如下

 typedef struct ucontext {
               struct ucontext *uc_link;
               sigset_t         uc_sigmask;
               stack_t          uc_stack;
               mcontext_t       uc_mcontext;
               ...
} ucontext_t;

該介面可獲取當前程式上下文並將其儲存到ucp中

2.將當前程式切換到某個上下文處的API

 int setcontext(const ucontext_t *ucp);

該API會將當前程式所執行的地方切換至ucp指向的上下文處

具體例項如下

#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  ucontext_t context;

  //獲取當前程式上下文
getcontext(&context); puts("Hello world"); sleep(1); //將程式切換至context指向的上下文處 setcontext(&context); return 0; }

該程式執行結果如下
這裡寫圖片描述

你可能會驚訝的發現這雜麼成了一個無線迴圈了!其實只要你理解了上面介紹的倆個API這一點都不奇怪
上述程式碼中我們先用getcontext獲取其所在位置的程式上下文,之後程式往下執行,當執行到setcontext時,我們上面以介紹過他的功能,其會將當前執行程式的位置挪到我們之前儲存下來的程式上下文context的內容,所以執行完此條語句後程序又會回到getcontext所在的語句處,也因此這段程式碼成了一個無限迴圈了

3.swapcontext

int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

該介面融合了上述介紹的1,2倆個介面的功能
我們可以將其功能理解為
首先執行getcontext(oucp);
然後執行setcontext(ucp);

4.將當前上下文入口改為某個函式入口的API

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

將API會將當前上下文ucp入口切換為func函式的額入口,及如果我們執行了setcontext(ucp)後,程式會去執行func函式
具體例項如下

   #include <stdio.h>
   #include <ucontext.h>
  #include <unistd.h>

  void fun(void)
  {
      printf("hello,world\n");
  }

  int main(int argc, char *argv[]) {
    ucontext_t context;
    char stack[1024];                                                                                                                         
    getcontext(&context);
    context.uc_stack.ss_sp = stack;
    context.uc_stack.ss_size = 1024;
    makecontext(&context,fun,0);
    setcontext(&context);
    return 0;
  }

程式執行結果為
這裡寫圖片描述

5.定時訊號的使用

關於定時訊號的使用非常簡單,我就不做太多介紹了
具體例項如下

   #include <stdio.h>
   #include <unistd.h>
  #include <signal.h>
  #include <sys/time.h>
  #include <functional>

  void print(int n)
  {
      printf("%s\n","hello,world");
  }

  int main(void)
  {
      int res = 0;
      struct itimerval tick;

      signal(SIGALRM,print);

      tick.it_value.tv_sec = 0;   //定時延時時間
      tick.it_value.tv_usec = 1;  //延時之後每多少長時間觸發一次定時事件

      tick.it_interval.tv_sec = 0;
      tick.it_interval.tv_usec = 100000;

      res = setitimer(ITIMER_REAL,&tick,NULL);                                                                                                
      if(res)
      {
          printf("set timer failed!!!\n");
      }


      while(1)
      {
          pause();
      }


      return 0;
  }

執行結果如下
這裡寫圖片描述

3.開始實現自己的執行緒

我用C++11來開發這個小程式

執行緒類的標頭檔案定義如下

 #pragma once

#include <functional>
#include <ucontext.h>
#include <map>
#include <memory>
#include <assert.h>
#include <stdio.h>

namespace mythread
{
    class Mythread
    {
        public:
            Mythread(int stackSize = 10240);
            ~Mythread();
            void start(void);   //開始執行執行緒類物件

            template <typename threadCallback,typename ...Args>
            void newThread(threadCallback cb,Args ...args)  //建立新執行緒
            {
                std::unique_ptr<ucontext_t> threadContext(new ucontext_t);

                assert(getcontext(threadContext.get()) != -1);  //獲取當前上下文

                //設定當前上下文資訊    
                threadContext->uc_stack.ss_sp = new char[stackSize_];
                threadContext->uc_stack.ss_size = stackSize_;
                threadContext->uc_link = 0;
                threadContext->uc_flags = 0;
                assert(threadContext->uc_stack.ss_sp != NULL);

                //將引數cb切換為當前上下文的入口函式
                makecontext(threadContext.get(),cb,sizeof...(args),args...);
                //將當前上下文插入map之中
                threadMap_.insert(std::pair<int,std::unique_ptr<ucontext_t>>(makeThreadId(),std::move(threadContext)));
            }

            void destroyThread(int id);  //銷燬某個執行緒

        private:
            static void threadSchedule(int n); //執行緒排程函式
            int makeThreadId(void);     //生成一個執行緒id
            static std::map<int,std::unique_ptr<ucontext_t>> threadMap_;  //儲存執行緒id及其對應的執行緒上下文內容
            static int current_;                       //儲存當前執行緒id
            int stackSize_;             //每個執行緒的棧空間的大小
            int maxThreadNumber_;               //最大可建立執行緒數
    };
}

該類的設計具體為

.用一個map類的靜態變數threadMap_來儲存每個我們通過newThread介面建立的執行緒的執行緒id以及對應的執行緒執行的上下文
.current_用來儲存我們當前所執行的執行緒的執行緒id
.makeThreadId函式用來生成一個執行緒id給我們新建立的執行緒使用
.threadSchedule用來排程各個執行緒的執行
.newThread介面是可以供外部呼叫的介面,用來建立一個執行緒
.start介面用來啟動定時器,以及通過定時訊號來呼叫上述排程執行緒的threadSchedule函式

該類的具體實現如下

#include <ucontext.h>
#include <signal.h>
#include "coroutine.h"
#include <assert.h>
#include <sys/time.h>
#include <signal.h>
#include <functional>
#include <memory>
#include <stdio.h>

using namespace mythread;

std::map<int,std::unique_ptr<ucontext_t>> Mythread::threadMap_;
int Mythread::current_ = 1;


Mythread::Mythread(int stackSize)
    :stackSize_(stackSize)
{

}

Mythread::~Mythread()
{
    for(auto &t : threadMap_)
    {
        free(t.second->uc_stack.ss_sp);   
    }
    threadMap_.clear(); //將map中的內容清空   
}

void Mythread::start(void)
{
    std::unique_ptr<ucontext_t> mainContextPtr(new ucontext_t);
    threadMap_.insert(std::pair<int,std::unique_ptr<ucontext_t>>(1,std::move(mainContextPtr)));//將主執行緒加入到map中

    signal(SIGALRM,threadSchedule);

    struct itimerval tick;
    tick.it_value.tv_sec = 0;   //無定時器延時
    tick.it_value.tv_usec = 1;

    tick.it_interval.tv_sec = 0;
    tick.it_interval.tv_usec = 1000;   //每10毫秒切一次執行緒 
    setitimer(ITIMER_REAL,&tick,NULL);
}


void Mythread::destroyThread(int id)
{
    free(threadMap_[id]->uc_stack.ss_sp);    //釋放執行緒棧空間
    threadMap_.erase(id);   //將map中的執行緒上下文刪除
}

int Mythread::makeThreadId(void)
{
    if((--threadMap_.end())->first != threadMap_.size())    //說明map中有未利用的id
    {
        int i = 1;
        for(auto it = threadMap_.begin(); it != threadMap_.end(); i++,it++)
        {
            if(i != it->first)
            {
                printf("return  = %d\n",i);
                return i;   
            }
        }
    }
    else
    {
        return threadMap_.size() + 1;
    }

    return 0;
}

void Mythread::threadSchedule(int n)
{
    if(threadMap_.size() > 1)
    {
        int lastId = current_;  //儲存當前id
        if(current_ != (--threadMap_.end())->first)//當前執行緒不是map中的最後一個
        {
            auto it = threadMap_.find(current_);
            current_ = (++it)->first;
        }
        else
        {
            current_ = threadMap_.begin()->first;   //從map的起點開始
        }

        swapcontext(threadMap_[lastId].get(),threadMap_[current_].get());   //切換執行緒
    }
}

測試程式碼如下

#include <iostream>
#include <vector>
#include <string>
#include "coroutine.h"
#include <unistd.h>

void func(void)
{
    while(true)
    {
        printf("hello\n");
    }
}

int main(int argc,char **argv)
{
  mythread::Mythread t;
  t.start();
  t.newThread(func);
  printf("hehe\n");

  while(1)
  {
    printf("world\n");
  }
  return 0;
}

如上述程式碼我們呼叫newThread介面建立一個執行緒去執行func函式,該函式迴圈列印hello,而主執行緒迴圈列印world
我們把定時器頻率調為10微妙
執行部分結果如下
這裡寫圖片描述
可以看出hello與world在無規律的交替列印

4.重點思路解析

我的設計其實很簡單,就是用一個map來儲存執行緒id和對應的執行緒上下文,每當我們建立新的執行緒(即呼叫makecontext之後),就將對應的執行緒id和上下文存入map。然後就是執行緒排程了,我的排程演算法也很簡單,就是給每個執行緒具體的時間片,然後輪流執行

5.總結

之所以會寫這個小玩意,是昨天突然看到了ucontext.h中的API,一時手癢就自己實現一下,其中有好多不足之處,之後也會不斷改進,並擴充,有興趣的可以一起搞
原始碼在這裡https://github.com/Miaoshuai/Coroutine

相關推薦

利用ucontext.h介面實現使用者級別執行

想必大家在初次接觸多執行緒或多程序時一定會感覺這是多麼的神奇!一個程式居然可以“同時”執行好多工作,此時你一定有一探其究竟的想法吧?可是無奈,這些都是核心中十分複雜的程式碼。對於初入計算機大門的我們,去看那些東西,的確有些困難。但是不用灰心,本篇博文我將為大家介

QT使用GDAL多執行讀取遙感影象到QImage

GDAL 是一個很強大的可以讀取很多格式 的帶有GIS資訊的柵格型影象。前陣子專案中需要讀取遙感影象,並顯示到QT介面,由於遙感影象一般很大,所以採取新開一個讀圖執行緒的方式來讀取,防止介面假死。下面是程式碼共享,測試通過讀取500MB的24000*24000畫素GeoTiff圖並在QT的QGr

qt 建立一個工作執行(例子)

當一個事件需要很長的處理時間,就建立一個工作執行緒,防止主介面卡死。 1.新建一個QT的gui專案,裡面包含main.cpp,mainwindow.h,mainwindow.cpp,mainwindow.ui檔案 2.新建一個頭檔案thread.h,派生一個執行緒類,重新寫一個執行緒的入口函式。

c#委託與多執行的實質

delegate(委託)的概念,.Net的委託本質上就是指向函式的指標,只不過這種指標是經過封裝後型別安全的。委託和執行緒是兩個不同的概念,執行緒是動態的,委託就是一個或一組記憶體地址,是靜態的。執行緒執行時如果遇到了指向函式的指標就執行這個函式。.Net為了方便程式設計,給委託賦予了兩種方式以供呼

tensorflow的佇列和執行

一、佇列 tensorflow中主要有FIFOQueue和RandomShuffleQueue兩種佇列,下面就詳細介紹這兩種佇列的使用方法和應用場景。 1、FIFOQueue FIFOQueue是先進先出佇列,主要是針對一些序列樣本。如:在使用迴圈神經網路的時候,需要處理語音、文字、

QTQtConcurrent建立並行執行的方法

標頭檔案中: #include <QtConcurrentRun> #include <QProcess> #include <QFuture> protected: bool event(QEvent *even

python 用threadingevent來實現執行同步

import threading import time event = threading.Event() #event.clear():將event的標誌設定為False,呼叫wait方法的所有執行緒將被阻塞; event.clear() def synchro_fun():

利用itertools生成密碼字典,多執行破解rar壓縮檔案密碼

指令碼功能:  利用itertools生成密碼字典(迭代器形式)  多執行緒併發從密碼字典中取出密碼進行驗證  驗證成功後把密碼寫入檔案中儲存 #!/usr/bin/env python # -*- coding: UTF-8 -*- # Author:Leslie-x import itert

Python併發、多執行

1、基本概念 併發和並行的區別: 1)並行,parallel 同時做某些事,可以互不干擾的同一時刻做幾件事。(解決併發的一種方法) 高速公路多個車道,車輛都在跑。同一時刻。 2)併發 concurrency 同時做某些事,一個時段內有事情要處理。(遇到的問題) 高併發,同一時刻內,有很多事情要處

java為什麼Hashtable是執行安全的,而HashMap是執行不安全的?還有ArrayList為什麼是執行不安全的,Vector是執行安全的??

文章目錄 一、HashMap解析 二、Hashtable解析 三、Collections.synchronizedMap()解析 四、ConcurrentHashMap 六、ArrayList為什麼是執行緒不安全的,Vector是執行緒安全的?

python的程序和執行

什麼是程序(process)? 程式並不能單獨執行,只有將程式裝載到記憶體中,系統為它分配資源才能執行,而這種執行的程式就稱之為程序。程式和程序的區別就在於:程式是指令的集合,它是程序執行的靜態描述文字;程序是程式的一次執行活動,屬於動態概念。 在多道程式設計中,我們允許多個程式

在try catch開啟新的執行,不能捕獲執行裡面的異常

近期在review程式碼的時候發現,有些人為了圖方便,直接在程式碼引用的最外層放上一個try catch,以為可以捕獲一切異常,降低崩潰率。 事實上,如果try的是新開啟的一個執行緒,那麼這個執行緒裡面出現的異常是catch不到。也就是說在A執行緒中new B執行緒,B執行緒中出現的cr

java那些類是執行安全的?

Java中各種集合(字串類)的執行緒安全性!!!   一、概念: 執行緒安全:就是當多執行緒訪問時,採用了加鎖的機制;即當一個執行緒訪問該類的某個資料時,會對這個資料進行保護,其他執行緒不能對其訪問,直到該執行緒讀取完之後,其他執行緒才可以使用。防止出現數據不一致或者資料被汙染的情況。

MFC建立和使用執行的方法

有關建立執行緒的問題有三種方法: 1.C語言函式,呼叫_beginthread(); 2.API函式,呼叫CreateThread(); 3.MFC函式,呼叫AfxBeginThread(); 推薦使用MFC函式AfxBeginThread(); 利用

關於多端開發遇到的一點執行問題

 在開發中我們會遇到很多不可思議的問題!可能前端對於執行緒理解的不是很深入,我先給大家講一下什麼是執行緒。  執行緒是程序內執行程式碼基本單位,那麼程序是什麼呢?指程式被OS調入記憶體,分配執行空間,隨時供CPU排程執行,也就是我們電腦所開啟的很多程序,這樣是不是更清楚了呢?(這裡就不深入講了,以後有機會給

java開啟一個新執行

//實現方法pubRmRecordByRmPlanId @Override public OperateResult pubRmRecordByRmPlanId(Long rmPlanId,String taskBeginDate,UserInfo userInfo) { ...

Java建立和啟動執行的兩種方式

方式1:繼承Thread類 步驟: 1):定義一個類A繼承於java.lang.Thread類. 2):在A類中覆蓋Thread類中的run方法. 3):我們在run方法中編寫需要執行的操作:run方法裡的程式碼,執行緒執行體. 4):在main方法(執行緒)中,建

淺析Java的四種執行

1.使用執行緒池的好處    2.JUC中幾種常用的執行緒池 java.util.concurrent包下的Executors工廠類,提供了一系列的執行緒池的建立方法,其構造方法如下: public ThreadPoolExecutor(int corePoolSize,   

執行總記憶體和執行的工作記憶體

Java記憶體模型將記憶體分為了 主記憶體和工作記憶體 。類的狀態,也就是類之間共享的變數,是儲存在主記憶體中的,每個執行緒都有一個自己的工作記憶體(相當於CPU高階緩衝區,這麼做的目的還是在於進一步縮小儲存系統與CPU之間速度的差異,提高效能),每次Java

AndroidCountDownLatch實現多執行同步

簡介 CountDownLatch 通過它的名字也能猜出一二來,Countdown 顧名思義倒計時,Latch可以理解為觸發或者發射。也就是說當倒數到0時就可以發射火箭啦,線上程中就是一個等待的執行緒,當 countdown 到 0 就不用再等待了,可以向下執行