1. 程式人生 > >記憶體分配 、同步、互斥

記憶體分配 、同步、互斥

malloc()

c 語言的malloc是glibc函式,其實際對應的系統呼叫是brk()函式(實際上是syscall 1)。glibc對brk系統呼叫進行封裝,然後抽象出malloc函式,提供給linux開發者使用。brk函式對應的是堆操作;

程序的空間 = 系統空間+使用者空間(棧:區域性變數和函式引數,暫存器+堆:動態記憶體,開發者分配+.bss+data常量區+text程式碼段)

32位系統只能定址4G空間,64位則是128G; 

 

 

(1)new、delete 是操作符,只能在C++中使用。malloc、free是函式,可以覆蓋,C、C++中都可以使用。
(2)new 自動計算需要分配的空間大小,可以呼叫物件的建構函式,對應的delete呼叫相應的解構函式。malloc僅僅分配記憶體,free僅僅回收記憶體,並不執行構造和解構函式
(3)new 型別安全、返回的是某種資料型別指標,malloc 非型別安全、返回的是void指標。

 

TCMalloc

TCMalloc全稱Thread-Caching Malloc,即執行緒快取的malloc,實現了高效的多執行緒記憶體管理,用於替代系統的記憶體分配相關的函式(malloc、free,new,new[]等)。TCMalloc是gperftools的一部分

TCMalloc記憶體分配演算法

按照所分配記憶體的大小,TCMalloc將記憶體分配分為三類:

  • 小物件分配,(0, 256KB]  
  • 中物件分配,(256KB, 1MB]
  • 大物件分配,(1MB, +∞)

簡要介紹幾個概念,Page,Span,PageHeap:

ThreadCache:每個執行緒的單獨快取,由FreeList按Size Class(8KB/16KB...256KB)儲存的空閒物件連結串列;執行緒內部取用回收不需要加鎖所以很快;

CentralCache: 所有執行緒的公用快取,由 CentralFreeList按Size Class(8KB/16KB...256KB)儲存的空閒物件連結串列;供各個執行緒的 ThreadCache 取用,所以取用或回收物件是需要加鎖的;

PageHeap:CentralCache不夠時向 PageHeap申請一塊記憶體,PageHeap使用sbrk或mmap向系統申請新的記憶體以生成新的span;PageHeap內部根據記憶體塊(span)採取不同的快取策略:128個 span以內,每個 size 用連結串列快取,超過128個 page 的 span,儲存與一個有序的 set 裡,PageHeap取用或釋放記憶體時需要加 spinlock,

互斥和同步

同步:任務協調同步,比如 A 依賴 B

互斥:訪問共享資源

互斥鎖(mutex)

上鎖(lock)和解鎖(unlock);原子性(原子操作)、唯一性、非繁忙等待(A 執行緒鎖定互斥量,則 B 執行緒試圖鎖時會被掛起即不佔用 cpu資源,直至互斥量解鎖喚醒B執行緒)

#include <pthread.h>

#include <time.h>

// 初始化一個互斥鎖。

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

 

// 對互斥鎖上鎖,若互斥鎖已經上鎖,則呼叫者一直阻塞,

// 直到互斥鎖解鎖後再上鎖。

int pthread_mutex_lock(pthread_mutex_t *mutex);

 

// 呼叫該函式時,若互斥鎖未加鎖,則上鎖,返回 0;

// 若互斥鎖已加鎖,則函式直接返回失敗,即 EBUSY。

int pthread_mutex_trylock(pthread_mutex_t *mutex);

 

// 當執行緒試圖獲取一個已加鎖的互斥量時,pthread_mutex_timedlock 互斥量

// 原語允許繫結執行緒阻塞時間。即非阻塞加鎖互斥量。

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);

 

// 對指定的互斥鎖解鎖。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

// 銷燬指定的一個互斥鎖。互斥鎖在使用完畢後,

// 必須要對互斥鎖進行銷燬,以釋放資源。

int pthread_mutex_destroy(pthread_mutex_t *mutex);


條件變數(同步)

條件變數用來自動阻塞一個執行緒,直 到某特殊情況發生為止

條件變數使用過程:1. 初始化:2. 等待條件成立3. 啟用條件變數:4. 清除條件變數:

#include <pthread.h>

// 初始化條件變數

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);

 

// 阻塞等待

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);

 

// 超時等待

int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

 

// 解除所有執行緒的阻塞

int pthread_cond_destroy(pthread_cond_t *cond);

 

// 至少喚醒一個等待該條件的執行緒

int pthread_cond_signal(pthread_cond_t *cond);

 

// 喚醒等待該條件的所有執行緒

int pthread_cond_broadcast(pthread_cond_t *cond);

 

讀寫鎖

#include <pthread.h>

// 初始化讀寫鎖

int pthread_rwlock_init(pthread_rwlock_t *rwlock,

                        const pthread_rwlockattr_t *attr);

 

// 申請讀鎖

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );

 

// 申請寫鎖

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );

 

// 嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖,

// 如果有任何的讀者或寫者持有該鎖,則立即失敗返回。

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

 

// 解鎖

int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);

 

// 銷燬讀寫鎖

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

 

自旋鎖

鎖的持有時間比較短,或者說小於2次上下文切換的時間。

spinlock又稱自旋鎖,執行緒通過busy-wait-loop的方式來獲取鎖,任時刻只有一個執行緒能夠獲得鎖,其他執行緒忙等待直到獲得鎖

spinlock在多處理器多執行緒環境的場景中有很廣泛的使用,一般要求使用spinlock的臨界區儘量簡短,這樣獲取的鎖可以儘快釋放,以滿足其他忙等的執行緒。Spinlock和mutex不同,spinlock不會導致執行緒的狀態切換(使用者態->核心態),但是spinlock使用不當(如臨界區執行時間過長)會導致cpu busy飆高。

訊號量(同步和互斥)

訊號量廣泛用於程序或執行緒間的同步和互斥,訊號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。

程式設計時可根據操作訊號量值的結果判斷是否對公共資源具有訪問的許可權,當訊號量值大於 0 時,則可以訪問,否則將阻塞。PV 原語是對訊號量的操作,一次 P 操作使訊號量減1,一次 V 操作使訊號量加1。
 

<

#include <semaphore.h>

// 初始化訊號量

int sem_init(sem_t *sem, int pshared, unsigned int value);

 

// 訊號量 P 操作(減 1)

int sem_wait(sem_t *sem);

 

// 以非阻塞的方式來對訊號量進行減 1 操作

int sem_trywait(sem_t *sem);

 

// 訊號量 V 操作(加 1)

int sem_post(sem_t *sem);

 

// 獲取訊號量的值

int sem_getvalue(sem_t *sem, int *sval);

 

// 銷燬訊號量

int sem_destroy(sem_t *sem);