Golang 中如何用 CGO 與 C 之間作一個緩存 buffer

Golang 是一個不錯的語言,尤爲是作一個緩存中間層是很是很是容易的。比較常見的場景就是咱們在讀一個很大很大的文件的時候,咱們是作不到一次加載文件到內存的,Golang 能夠作到一點一點的將文件讀至末尾,慢慢處理完,相信不少語言也很容易作到這個,那若是在處理這個文件的時候項目的主語言是 Golang 而須要用到一些用 C 寫好的模塊那又該如何呢?若是讓一個程序員只用 C 來實現處理一個大文件,那應該也是很容易的。Golang 對 C 的 binding 呢?git

首先,咱們先定義一個 C 的數據結構,也是一個很經典的數據結構:程序員

typedef struct buffer_data { // 緩存數據的結構體
  uint8_t *ptr;
  size_t size;
} buffer_data;
複製代碼

既然是緩存那就應該有一個明確的大小,至少會是一個固定的大小,更復雜的場景可能會根據具體的外部參數形成緩存大小的變化。如今咱們只是寫一個例子而已,簡單至上。而後就須要寫一個針對上邊的數據結構的初始化函數了。github

// 初始化傳入的 buffer 的內存對象
buffer_data init_buffer() {
  buffer_data buffer_in; // 傳入的數據對象
  buffer_in.ptr  = malloc(MAX_BUFFER_SIZE * sizeof(uint8_t));
  buffer_in.size = 0;
  return buffer_in;
}
複製代碼

代碼到此爲止都很簡單,僅僅是申請一些空間給這個緩存,而且緩存大小固定。後邊的就稍微有一點點難度了。緩存

緩存的話,就須要兩個函數寫入讀出來操做。數據結構

讀入的操做來講就是將新的數據添加到緩存的尾部,首先看一下代碼:多線程

// 從 Golang 中傳入數據到 c 的內存中,返回每次讀取的數據的數量
// 鑑於內存中不能夠緩存過多的數據,也是爲了節省內存,那麼就須要每次僅將 buffer 填充的必定長度便可
// buffer 數據寫入的目的位置
// buf 寫入的數據
// buf_size 寫入數據的大小
// return 已經寫入數據的長度
int buffer_append(buffer_data *buffer, uint8_t *buf, int buf_size) {
  if (buffer->size == MAX_BUFFER_SIZE) {
    return 0;
  }
  pthread_mutex_lock(&buffer_in_mutex);
  if (MAX_BUFFER_SIZE - buffer->size > buf_size) {
    memcpy(buffer->ptr + buffer->size, buf, buf_size);
    buffer->size += buf_size;
    pthread_mutex_unlock(&buffer_in_mutex); // 解鎖線程
    return buf_size;
  }
  memcpy(buffer->ptr + buffer->size, buf, MAX_BUFFER_SIZE - buffer->size);
  int read     = MAX_BUFFER_SIZE - buffer->size;
  buffer->size = MAX_BUFFER_SIZE;
  pthread_mutex_unlock(&buffer_in_mutex); // 解鎖線程
  return read;
}
複製代碼

因爲寫入和讀出的操做是針對一個競態變量的互斥操做,那咱們爲了防止多線程操做的時候有問題,就須要針對 buffer 操做的時候加上一個線程鎖。代碼的其餘部分就比較容易理解了,僅僅是一些內存複製之類的。app

最後就是那個讀出的操做了,讀出的操做稍稍有一點點複雜相比寫入,常規的作法就是將緩存的頭部的數據取出一些,而後將後邊的未被讀到的數據往前移動,OK,看代碼:函數

// 讀出數據
// buffer 數據源
// buf 數據讀出以後存儲的位置
// buf_size 傳入的 buf 的申請的空間的大小
// return 讀出的數據的長度
int buffer_read(buffer_data *buffer, uint8_t *buf, int buf_size) {
  if (buf_size == 0) {
    return 0;
  }
  pthread_mutex_lock(&buffer_in_mutex);
  if (buf_size >= buffer->size) {
    int read = buffer->size;
    memcpy(buf, buffer->ptr, buffer->size);
    buffer->size = 0;
    pthread_mutex_unlock(&buffer_in_mutex); // 解鎖線程
    return read;
  }
  memcpy(buf, buffer->ptr, buf_size); 
  memmove(buffer->ptr,buffer->ptr+buf_size,buffer->size-buf_size);
  buffer->size -= buf_size;
  pthread_mutex_unlock(&buffer_in_mutex); // 解鎖線程
  return buf_size;
}
複製代碼

到此爲止,咱們的 C 的部分就完成了,其實這個仍是有一點點簡陋,真正的應該是 buffer_inbuffer_out 一個輸入的緩存,一個是輸出的緩存,中間 C 對輸入的緩存作一些處理,好比音頻格式轉換之類的,而後將數據給到 buffer_out 中,在 Golang 中接收數據作一些其餘處理。ui

在這個例子中咱們的 Golang 的做用僅僅是將數據一步步放到 buffer 中而後從 buffer 再讀出來。spa

package main

// #include <reader.h>
import "C"
import (
	"io/ioutil"
	"os"
	"unsafe"
)

func main() {
	var buffer = C.init_buffer()
	bytes, _ := ioutil.ReadFile("reader.h.gch")

	f, _ := os.Create("reader.out")
	for len(bytes) != 0 {
		var write = int(C.buffer_append(&buffer, (*C.uchar)(unsafe.Pointer(&bytes[0])), C.int(len(bytes))))
		bytes = bytes[write:]
		for {
			var bytes = make([]byte, 1024)
			var read = int(C.buffer_read(&buffer, (*C.uchar)(unsafe.Pointer(&bytes[0])), C.int(len(bytes))))
			if read == 0 {
				break
			}
			f.Write(bytes[:read])
		}
	}
	f.Close()
}
複製代碼

OK,代碼到此爲止就已經完了,大概的寫法就是這樣,裏邊沒什麼難點,只是有些同窗開始作 CGO 的時候不太會寫二進制的數據如何在 C 和 Golang 中傳遞而已。

完整的項目請查看這裏: github.com/tosone/read…

另外要說明的一點是 Golang 和 C 在傳遞參數的時候是內存拷貝各自管理內存,CGO 底層有作中間內存的處理。因此若是你的程序關於 CGO 那一部分老是出現 Segmentation fault 那就是 C 的內存沒有管理好,仔細查查,也有多是 CGO 底層出了問題,這個機率就比較小了,若是是 Golang 這邊出現了問題通常都會有錯誤的堆棧的打印。這個小例子裏邊沒有考慮太多的內存釋放這方面的問題,實際項目中參考這段代碼的時候千萬注意,坑了別找我。

相關文章
相關標籤/搜索