無鎖編程:最簡單例子

來源:互聯網
上載者:User
情境

    假設存在這樣一個情況:需要N個線程對一個全域的變數進行M次遞增操作。首先想到的常常是,使用互斥量。當然在“無鎖”的世界裡,還有其它實現方式。話不多說,看代碼:

測試代碼

gcc_sync_test.c

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#define TEST_ROUND  20000#define THREAD_NUM  10#define SYNC#define LOCKLESS#ifndef LOCKLESSpthread_mutex_t mutex_lock;#endifstatic volatile int count = 0;void *test_func(void *arg){int i = 0;for(i = 0; i < TEST_ROUND; i++){#ifdef SYNC#ifdef LOCKLESS__sync_fetch_and_add(&count, 1);#elsepthread_mutex_lock(&mutex_lock);count++;pthread_mutex_unlock(&mutex_lock);#endif#elsecount++;#endif}return NULL;}int main(int argc, const char *argv[]){pthread_t thread_ids[THREAD_NUM];int i = 0;#ifndef LOCKLESSpthread_mutex_init(&mutex_lock, NULL);#endiffor(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){pthread_create(&thread_ids[i], NULL, test_func, NULL);}for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){pthread_join(thread_ids[i], NULL);}printf("count=%d\r\n", count);return 0;}

Makefile

CC=gccCFLAGS= -WallLIB= -lpthreadOBJS=gcc_sync_test.o SRCS=${OBJS:%.o=%.c}TARGETS=.depend gcc_sync_testall:$(TARGETS).depend:@$(CC) $(CFLAGS) -MM $(SRCS)  > .depend  -include .dependgcc_sync_test: $(OBJS)$(CC) $(CFLAGS) $^ $(LIB) -o $@@echo $@ > .gitignoreclean:rm -rf $(OBJS) $(TARGETS).c.o:$(CC) $(CFLAGS) -c $< -o $@

測試結果

    在原始碼檔案gcc_sync_test.c中,使用SYNC宏來控制是否啟用線程間同步;在啟用SYNC情況下,使用LOCKLESS宏來控制是否使用“無鎖”方式,還是使用互斥量方式。

選擇“無鎖”方式,編譯、運行程式可以得到正確的結果“count=200000”,這和使用互斥量方式得到的結果一樣。如果感興趣的話,可以試試不採用任何一種同步方案(即,注釋掉SYNC宏的定義),可以發現輸出的結果是不正確的,道理很顯然。

結果分析

    那麼,為什麼不用phtread_mutex_lock也可以實現線程間同步?可以看到程式中使用了__sync_fetch_and_add在實現加法運算。__sync_fetch_and_add是GCC內建的原子操作,它的原理《GCC內建的原子操作》中已經做了簡單的敘述。如果關注GCC是如何?該原子操作的,可以通過產生彙編代碼的方式來探究。

gcc -S gcc_sync_test.c

產生彙編代碼gcc_sync_test.s。查看它,可以發現其中有如下代碼:

   jmp .L2.L3:   lock addl $1, count(%rip)   addl  $1, -4(%rbp).L2:   cmpl  $19999, -4(%rbp)

其中,“lock addl $1, count(%rip)”中的lock既是關鍵所在。lock是一個指令首碼,Intel的手冊上對其的解釋是:

Causes the processor's LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal insures that the processor has exclusive use of any shared memory while the signal is asserted.

如果不使用原子操作__sync_fetch_and_add,直接進行count++的話,產生的彙編代碼大致是這樣的:

  jmp .L2 .L3:  movl  count(%rip), %eax  addl  $1, %eax  movl  %eax, count(%rip)  addl  $1, -4(%rbp).L2:  cmpl  $19999, -4(%rbp)

顯然缺了lock指令首碼。

其它

    至於,為什麼count變數要是volatile的,這是避免使用gcc最佳化選項後直接將M此迴圈的結果算出,影響了執行個體代碼的顯著性。讀者可以自己嘗試一下:去掉volatile修飾,gcc編譯時間使用-O2最佳化,不使用任何同步的情況下(不啟用SYNC宏),似乎也能得到正確的結果。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.