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_in
和 buffer_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 這邊出現了問題通常都會有錯誤的堆棧的打印。這個小例子裏邊沒有考慮太多的內存釋放這方面的問題,實際項目中參考這段代碼的時候千萬注意,坑了別找我。