日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux原子操作与锁实现

發布時間:2023/12/20 linux 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux原子操作与锁实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原子操作CAS與鎖

  • 多線程的使用
    • 線程的創建
    • 線程的終止
      • pthread_exit()
      • pthread_cancel()
    • 線程的等待
    • 線程的屬性
  • 無原子操作
  • 互斥鎖
    • pthread_mutex_init()
    • pthread_mutex_destroy()
    • pthread_mutex_lock()和pthread_mutex_trylock()
    • pthread_mutex_unlock()
    • 示例代碼
  • 自旋鎖
    • 示例代碼
  • 互斥鎖與自旋鎖的區別
  • 死鎖
  • 原子操作
  • 總結
  • 后言

多線程的使用

線程的創建

函數原型:

#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg); // Compile and link with -pthread.

描述:
pthread_create()函數在調用進程中啟動一個新線程。新線程通過調用start_routine()開始執行;arg作為start_routine()的唯一參數傳遞。

新線程以以下方式之一終止:
(1)它調用pthread_exit(),指定一個退出狀態值,該值可用于調用pthrread_join()的同一進程中的另一個線程,即pthrread_join()可以接收pthread_exit()返回的值。
(2)它從start_routine()返回。這相當于使用return語句中提供的值調用pthread_exit()。
(3)它被pthread_cancel()取消。
(4)進程中的任何線程都調用exit(),或者主線程執行main()的返回。這將導致進程中所有線程的終止。

參數介紹:

參數含義
attrattr參數指向pthread_attr_t結構,其內容在線程創建時用于確定新線程的屬性;使用pthread_attr_init()和相關函數初始化該結構。如果attr為空,則使用默認屬性創建線程。
thread在返回之前,成功調用pthread_create()將新線程的ID存儲在thread指向的緩沖區中;此標識符用于在后續調用其他pthreads函數時引用線程。
start_routine線程入口函數
arg線程入口函數的參數

返回值:
成功時,返回0;出錯時,它返回一個錯誤號,并且*thread的內容未定義。

錯誤號:

錯誤號含義
EAGAIN資源不足,無法創建另一個線程。
AGAIN A遇到系統對線程數量施加的限制。可能觸發此錯誤的限制有很多:已達到RLIMIT_NPROC軟資源限制【通過setrlimit()設置】,該限制限制了真實用戶ID的進程和線程數;已達到內核對進程和線程數的系統范圍限制,即/proc/sys/kernel/threads max【請參閱proc()】;或者達到最大pid數/proc/sys/kernel/pid_max【見proc()】。
EINVAL屬性中的設置無效。
EPERM沒有設置attr中指定的調度策略和參數的權限。

其他:
新線程繼承創建線程的信號掩碼【pthread_sigmask()】的副本。新線程的掛起信號集為空【sigpending()】。新線程不繼承創建線程的備用信號堆棧【sigaltstack()】。
新線程的CPU時間時鐘的初始值為0【參見pthread_getcpuclockid()】。

示例代碼:

#include <pthread.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <ctype.h>#define handle_error_en(en, msg) \do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)struct thread_info { /* Used as argument to thread_start() */pthread_t thread_id; /* ID returned by pthread_create() */int thread_num; /* Application-defined thread # */char *argv_string; /* From command-line argument */ };/* Thread start function: display address near top of our stack,and return upper-cased copy of argv_string */static void * thread_start(void *arg) {struct thread_info *tinfo = arg;char *uargv, *p;printf("Thread %d: top of stack near %p; argv_string=%s\n",tinfo->thread_num, &p, tinfo->argv_string);uargv = strdup(tinfo->argv_string);if (uargv == NULL)handle_error("strdup");for (p = uargv; *p != '\0'; p++)*p = toupper(*p);return uargv; }int main(int argc, char *argv[]) {int s, tnum, opt, num_threads;struct thread_info *tinfo;pthread_attr_t attr;int stack_size;void *res;/* The "-s" option specifies a stack size for our threads */stack_size = -1;while ((opt = getopt(argc, argv, "s:")) != -1) {switch (opt) {case 's':stack_size = strtoul(optarg, NULL, 0);break;default:fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",argv[0]);exit(EXIT_FAILURE);}}num_threads = argc - optind;/* Initialize thread creation attributes */s = pthread_attr_init(&attr);if (s != 0)handle_error_en(s, "pthread_attr_init");if (stack_size > 0) {s = pthread_attr_setstacksize(&attr, stack_size);if (s != 0)handle_error_en(s, "pthread_attr_setstacksize");}/* Allocate memory for pthread_create() arguments */tinfo = calloc(num_threads, sizeof(struct thread_info));if (tinfo == NULL)handle_error("calloc");/* Create one thread for each command-line argument */for (tnum = 0; tnum < num_threads; tnum++) {tinfo[tnum].thread_num = tnum + 1;tinfo[tnum].argv_string = argv[optind + tnum];/* The pthread_create() call stores the thread ID intocorresponding element of tinfo[] */s = pthread_create(&tinfo[tnum].thread_id, &attr,&thread_start, &tinfo[tnum]);if (s != 0)handle_error_en(s, "pthread_create");}/* Destroy the thread attributes object, since it is nolonger needed */s = pthread_attr_destroy(&attr);if (s != 0)handle_error_en(s, "pthread_attr_destroy");/* Now join with each thread, and display its returned value */for (tnum = 0; tnum < num_threads; tnum++) {s = pthread_join(tinfo[tnum].thread_id, &res);if (s != 0)handle_error_en(s, "pthread_join");printf("Joined with thread %d; returned value was %s\n",tinfo[tnum].thread_num, (char *) res);free(res); /* Free memory allocated by thread */}free(tinfo);exit(EXIT_SUCCESS); }

線程的終止

新線程以以下方式之一終止:
(1)它調用pthread_exit(),指定一個退出狀態值,該值可用于調用pthrread_join()的同一進程中的另一個線程,即pthrread_join()可以接收pthread_exit()返回的值。
(2)它從start_routine()返回。這相當于使用return語句中提供的值調用pthread_exit()。
(3)它被pthread_cancel()取消。
(4)進程中的任何線程都調用exit(),或者主線程執行main()的返回。這將導致進程中所有線程的終止。

pthread_exit()

函數原型:

#include <pthread.h>void pthread_exit(void *retval);// Compile and link with -pthread.

描述:
(1)pthread_exit()函數終止調用線程并通過retval返回一個值,該值(如果線程是可連接的)可用于調用pthrea_join()的同一進程中的另一個線程,即可被pthrea_join()接收返回值。

(2)任何由pthread_cleanup_push()建立的尚未彈出的清理處理程序都會彈出(與它們被推送的順序相反)并執行。如果線程具有任何特定于線程的數據,則在執行清理處理程序后,將以未指定的順序調用相應的析構函數。

(3)當線程終止時,進程共享資源(例如互斥體、條件變量、信號量和文件描述符)不會被釋放,使用atexit()注冊的函數也不會被調用。

(4)進程中的最后一個線程終止后,進程通過調用exit()終止,退出狀態為零;因此,釋放進程共享資源并調用使用atexit()注冊的函數。

返回值:
此函數不返回調用方。

錯誤:
此函數始終成功。

注意:
(1)從除主線程之外的任何線程的start函數執行返回將導致隱式調用pthread_exit(),使用函數的返回值作為線程的退出狀態。
(2)為了允許其他線程繼續執行,主線程應該通過調用pthread_exit()而不是exit()來終止。
(3)retval指向的值不應位于調用線程的堆棧上,因為該堆棧的內容在線程終止后未定義。

pthread_cancel()

函數原型:

#include <pthread.h>int pthread_cancel(pthread_t thread);// Compile and link with -pthread.

描述:
pthread_cancel()函數向線程thread發送取消請求。目標線程是否以及何時響應取消請求取決于該線程控制的兩個屬性:其可取消性state和type。

由pthread_setcancelstate()設置線程的可取消狀態可以啟用(新線程的默認狀態)或禁用。如果線程已禁用取消,則取消請求將保持排隊狀態,直到線程啟用取消。如果線程已啟用取消,則其可取消性類型決定何時取消。

由pthread_setcanceltype()確定的線程的取消類型可以是異步的或延遲的(新線程的默認值)。異步可取消性意味著線程可以隨時取消(通常是立即取消,但系統不保證)。延遲可取消性意味著取消將被延遲,直到線程下一次調用作為取消點的函數。pthreads()中提供了作為或可能是取消點的函數列表。

執行取消請求時,線程將執行以下步驟(按順序):

#mermaid-svg-QThAQindSLNVCmE7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-QThAQindSLNVCmE7 .error-icon{fill:#552222;}#mermaid-svg-QThAQindSLNVCmE7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QThAQindSLNVCmE7 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-QThAQindSLNVCmE7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QThAQindSLNVCmE7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QThAQindSLNVCmE7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QThAQindSLNVCmE7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QThAQindSLNVCmE7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QThAQindSLNVCmE7 .marker.cross{stroke:#333333;}#mermaid-svg-QThAQindSLNVCmE7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QThAQindSLNVCmE7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QThAQindSLNVCmE7 .cluster-label text{fill:#333;}#mermaid-svg-QThAQindSLNVCmE7 .cluster-label span{color:#333;}#mermaid-svg-QThAQindSLNVCmE7 .label text,#mermaid-svg-QThAQindSLNVCmE7 span{fill:#333;color:#333;}#mermaid-svg-QThAQindSLNVCmE7 .node rect,#mermaid-svg-QThAQindSLNVCmE7 .node circle,#mermaid-svg-QThAQindSLNVCmE7 .node ellipse,#mermaid-svg-QThAQindSLNVCmE7 .node polygon,#mermaid-svg-QThAQindSLNVCmE7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QThAQindSLNVCmE7 .node .label{text-align:center;}#mermaid-svg-QThAQindSLNVCmE7 .node.clickable{cursor:pointer;}#mermaid-svg-QThAQindSLNVCmE7 .arrowheadPath{fill:#333333;}#mermaid-svg-QThAQindSLNVCmE7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QThAQindSLNVCmE7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QThAQindSLNVCmE7 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-QThAQindSLNVCmE7 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-QThAQindSLNVCmE7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QThAQindSLNVCmE7 .cluster text{fill:#333;}#mermaid-svg-QThAQindSLNVCmE7 .cluster span{color:#333;}#mermaid-svg-QThAQindSLNVCmE7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QThAQindSLNVCmE7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}pthread_cancel()取消清理處理程序被彈出(與它們被推送的順序相反)并被調用。參見pthread_cleanup_push()以未指定的順序調用線程特定的數據析構函數。參見pthread_key_create()終止線程。參見pthread_exit()

上述步驟相對于pthread_cancel()調用異步發生;pthread_cancel()的返回狀態僅通知調用方取消請求是否已成功排隊。

被取消的線程終止后,使用pthread_join()與該線程的連接將獲得pthrea_canceled作為線程的退出狀態。(使用線程連接是知道取消已完成的唯一方法。)

返回值:
成功時,返回0;出錯時,返回非零錯誤號。

錯誤:
ESRCH,找不到ID為thread的線程。

線程的等待

函數原型:

#include <pthread.h>int pthread_join(pthread_t thread, void **retval);// Compile and link with -pthread.

描述:
pthread_join()函數等待線程指定的線程終止。如果該線程已經終止,則pthread_join()立即返回。thread指定的線程必須是可連接的。

如果retval不為空,則pthread_join()將目標線程的退出狀態(即,目標線程提供給pthrea_exit()的值)復制到retval所指向的位置。如果目標線程被取消,則PTHREAD_CANCELED被置于retval中。

如果多個線程同時嘗試與同一線程聯接,則結果是未定義的。如果調用pthread_join()的線程被取消,那么目標線程將保持可連接狀態(即,它不會被分離)。

返回值:
成功時,返回0;出錯時,它返回錯誤號。

錯誤號:

錯誤號含義
EDEADLK檢測到死鎖(例如,兩個線程試圖彼此連接);或thread指定調用線程。
EINVAL線程不是可連接線程。
EINVAL另一個線程已在等待加入此線程。
ESRCH找不到ID為線程的線程。

線程的屬性

函數原型:

#include <pthread.h>int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr);// Compile and link with -pthread.

描述:
pthread_attr_init()函數使用默認屬性值初始化attr指向的線程屬性對象。在這個調用之后,可以使用各種相關函數(下方列出)設置對象的各個屬性,然后可以在創建線程的一個或多個pthread_create()調用中使用該對象。

pthread_attr_setaffinity_np(), pthread_attr_setdetachstate(), pthread_attr_setguardsize(), pthread_attr_setinheritsched(), pthread_attr_setschedparam(), pthread_attr_setschedpolicy(), pthread_attr_setscope(), pthread_attr_setstack(), pthread_attr_setstackaddr(), pthread_attr_setstacksize(), pthread_create(), pthread_getattr_np(), pthreads()

對已初始化的線程屬性對象調用pthread_attr_init()會導致未定義的行為。

當不再需要線程屬性對象時,應使用pthread_attr_destroy()函數將其銷毀。 銷毀線程屬性對象對使用該對象創建的線程沒有影響。

線程屬性對象被銷毀后,可以使用pthread_attr_init()對其重新初始化。任何其他使用已銷毀線程屬性對象的方法都會產生未定義的結果。

返回值:
成功時,這些函數返回0;出錯時,它們返回一個非零錯誤號。

錯誤:
在Linux上,這些函數總是成功的(但可移植和未來驗證的應用程序應該處理可能的錯誤返回)。

pthread_attr_t類型應被視為不透明的:除通過pthreads函數外,對對象的任何訪問都是不可移植的,并產生未定義的結果。

示例代碼:

#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */ #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h>#define handle_error_en(en, msg) \do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)static void display_pthread_attr(pthread_attr_t *attr, char *prefix) {int s, i;size_t v;void *stkaddr;struct sched_param sp;s = pthread_attr_getdetachstate(attr, &i);if (s != 0)handle_error_en(s, "pthread_attr_getdetachstate");printf("%sDetach state = %s\n", prefix,(i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :(i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :"???");s = pthread_attr_getscope(attr, &i);if (s != 0)handle_error_en(s, "pthread_attr_getscope");printf("%sScope = %s\n", prefix,(i == PTHREAD_SCOPE_SYSTEM) ? "PTHREAD_SCOPE_SYSTEM" :(i == PTHREAD_SCOPE_PROCESS) ? "PTHREAD_SCOPE_PROCESS" :"???");s = pthread_attr_getinheritsched(attr, &i);if (s != 0)handle_error_en(s, "pthread_attr_getinheritsched");printf("%sInherit scheduler = %s\n", prefix,(i == PTHREAD_INHERIT_SCHED) ? "PTHREAD_INHERIT_SCHED" :(i == PTHREAD_EXPLICIT_SCHED) ? "PTHREAD_EXPLICIT_SCHED" :"???");s = pthread_attr_getschedpolicy(attr, &i);if (s != 0)handle_error_en(s, "pthread_attr_getschedpolicy");printf("%sScheduling policy = %s\n", prefix,(i == SCHED_OTHER) ? "SCHED_OTHER" :(i == SCHED_FIFO) ? "SCHED_FIFO" :(i == SCHED_RR) ? "SCHED_RR" :"???");s = pthread_attr_getschedparam(attr, &sp);if (s != 0)handle_error_en(s, "pthread_attr_getschedparam");printf("%sScheduling priority = %d\n", prefix, sp.sched_priority);s = pthread_attr_getguardsize(attr, &v);if (s != 0)handle_error_en(s, "pthread_attr_getguardsize");printf("%sGuard size = %d bytes\n", prefix, v);s = pthread_attr_getstack(attr, &stkaddr, &v);if (s != 0)handle_error_en(s, "pthread_attr_getstack");printf("%sStack address = %p\n", prefix, stkaddr);printf("%sStack size = 0x%zx bytes\n", prefix, v); }static void * thread_start(void *arg) {int s;pthread_attr_t gattr;/* pthread_getattr_np() is a non-standard GNU extension thatretrieves the attributes of the thread specified in itsfirst argument */s = pthread_getattr_np(pthread_self(), &gattr);if (s != 0)handle_error_en(s, "pthread_getattr_np");printf("Thread attributes:\n");display_pthread_attr(&gattr, "\t");exit(EXIT_SUCCESS); /* Terminate all threads */ }int main(int argc, char *argv[]) {pthread_t thr;pthread_attr_t attr;pthread_attr_t *attrp; /* NULL or &attr */int s;attrp = NULL;/* If a command-line argument was supplied, use it to set thestack-size attribute and set a few other thread attributes,and set attrp pointing to thread attributes object */if (argc > 1) {int stack_size;void *sp;attrp = &attr;s = pthread_attr_init(&attr);if (s != 0)handle_error_en(s, "pthread_attr_init");s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);if (s != 0)handle_error_en(s, "pthread_attr_setdetachstate");s = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);if (s != 0)handle_error_en(s, "pthread_attr_setinheritsched");stack_size = strtoul(argv[1], NULL, 0);s = posix_memalign(&sp, sysconf(_SC_PAGESIZE), stack_size);if (s != 0)handle_error_en(s, "posix_memalign");printf("posix_memalign() allocated at %p\n", sp);s = pthread_attr_setstack(&attr, sp, stack_size);if (s != 0)handle_error_en(s, "pthread_attr_setstack");}s = pthread_create(&thr, attrp, &thread_start, NULL);if (s != 0)handle_error_en(s, "pthread_create");if (attrp != NULL) {s = pthread_attr_destroy(attrp);if (s != 0)handle_error_en(s, "pthread_attr_destroy");}pause(); /* Terminates when other thread calls exit() */ }

無原子操作

在多個線程中,對一個變量不斷操作,如果沒有原子操作會怎么樣?
示例代碼:

#include <stdio.h> #include <pthread.h> #include <unistd.h>#define THREAD_SIZE 10// 10 * 100000 void *func(void *arg) {int *pcount = (int *)arg;int i = 0;while (i++ < 100000) {(*pcount)++;usleep(1);}}int main(int argc, char **argv) {pthread_t threadid[THREAD_SIZE] = { 0 };int i = 0;int count = 0;for (i = 0; i < THREAD_SIZE; i++) {pthread_create(&threadid[i], NULL, func, &count);}// 1000w for (i = 0; i < 10; i++) {printf("count = %d\n", count);sleep(1);}return 0; }

上述代碼執行結果理論上是1000000,但是最后結果是994656。也就是無原子操作下的執行結果小于理論值。
原因在于,執行idx++時匯編代碼是:

Mov [idx], %eax Inc %eax Mov %eax,[idx]

也就是c語言是一條語句,但真正執行時是三條命令。在無原子操作時,就可能出現如下情況:

#mermaid-svg-6I9wuHynbZ6ZGxvO {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO .error-icon{fill:#552222;}#mermaid-svg-6I9wuHynbZ6ZGxvO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6I9wuHynbZ6ZGxvO .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-6I9wuHynbZ6ZGxvO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6I9wuHynbZ6ZGxvO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6I9wuHynbZ6ZGxvO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6I9wuHynbZ6ZGxvO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6I9wuHynbZ6ZGxvO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6I9wuHynbZ6ZGxvO .marker.cross{stroke:#333333;}#mermaid-svg-6I9wuHynbZ6ZGxvO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6I9wuHynbZ6ZGxvO .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6I9wuHynbZ6ZGxvO text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6I9wuHynbZ6ZGxvO .actor-line{stroke:grey;}#mermaid-svg-6I9wuHynbZ6ZGxvO .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO .sequenceNumber{fill:white;}#mermaid-svg-6I9wuHynbZ6ZGxvO #sequencenumber{fill:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO .messageText{fill:#333;stroke:#333;}#mermaid-svg-6I9wuHynbZ6ZGxvO .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6I9wuHynbZ6ZGxvO .labelText,#mermaid-svg-6I9wuHynbZ6ZGxvO .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6I9wuHynbZ6ZGxvO .loopText,#mermaid-svg-6I9wuHynbZ6ZGxvO .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6I9wuHynbZ6ZGxvO .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6I9wuHynbZ6ZGxvO .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6I9wuHynbZ6ZGxvO .noteText,#mermaid-svg-6I9wuHynbZ6ZGxvO .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6I9wuHynbZ6ZGxvO .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6I9wuHynbZ6ZGxvO .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6I9wuHynbZ6ZGxvO .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6I9wuHynbZ6ZGxvO .actorPopupMenu{position:absolute;}#mermaid-svg-6I9wuHynbZ6ZGxvO .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6I9wuHynbZ6ZGxvO .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6I9wuHynbZ6ZGxvO .actor-man circle,#mermaid-svg-6I9wuHynbZ6ZGxvO line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6I9wuHynbZ6ZGxvO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}線程1線程2Mov [idx], %eax切換到Mov [idx], %eaxInc %eaxMov %eax,[idx]切換到Inc %eaxMov %eax,[idx]線程1線程2

原意要自增兩次,然而實際只自增了一次,因此無原子操作下的執行結果小于理論值。

互斥鎖

讓臨界資源只允許在一個線程中執行。

pthread_mutex_init()

函數原型:

#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

函數描述:
互斥鎖的初始化。
pthread_mutex_init() 函數是以動態方式創建互斥鎖的,參數attr指定了新建互斥鎖的屬性。如果參數attr為空(NULL),則使用默認的互斥鎖屬性,默認屬性為快速互斥鎖 。
互斥鎖的屬性在創建鎖的時候指定,在實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。

返回:
成功會返回零,其他任何返回值都表示出現了錯誤。
成功后,互斥鎖被初始化為未鎖住態。

pthread_mutex_destroy()

用于注銷一個互斥鎖,函數原型:

#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex)

銷毀一個互斥鎖即意味著釋放它所占用的資源,且要求鎖當前處于開放狀態。由于在Linux中,互斥鎖并不占用任何資源,因此pthread_mutex_destroy()僅僅檢查鎖狀態(鎖定狀態則返回EBUSY)。

pthread_mutex_lock()和pthread_mutex_trylock()

函數原型:

#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex);

描述:
互斥引用的互斥對象通過調用 pthread_mutex_lock()被鎖定。如果互斥鎖已被鎖定,則調用線程將阻塞,直到互斥體變為可用。此操作將返回由處于鎖定狀態的互斥所引用的互斥對象,其中調用線程是其所有者。
函數 pthread_mutex_trylock()與 pthread_mutex_lock()相同,只是如果互斥引用的互斥對象當前被鎖定(由任何線程,包括當前線程鎖定),則調用將立即返回。

互斥類型含義
PTHREAD_MUTEX_NORMAL不提供死鎖檢測。嘗試重新鎖定互斥鎖會導致死鎖。如果線程嘗試解鎖它尚未鎖定的互斥鎖或已解鎖的互斥體,則會導致未定義的行為。
PTHREAD_MUTEX_ERRORCHECK提供錯誤檢查。如果線程嘗試重新鎖定已鎖定的互斥鎖,則會返回錯誤。如果線程嘗試解鎖尚未鎖定的互斥體或已解鎖的互斥體,則將返回錯誤。
PTHREAD_MUTEX_RECURSIVE互斥鎖將保留鎖定計數的概念。當線程首次成功獲取互斥鎖時,鎖定計數將設置為 1。每次線程重新鎖定此互斥鎖時,鎖定計數都會遞增 1。每次線程解鎖互斥體時,鎖定計數都會減少 1。當鎖定計數達到零時,互斥鎖將可供其他線程獲取。如果線程嘗試解鎖尚未鎖定的互斥體或已解鎖的互斥體,則將返回錯誤。
PTHREAD_MUTEX_DEFAULT嘗試遞歸鎖定互斥會導致未定義的行為。如果互斥體未被調用線程鎖定,則嘗試解鎖該互斥體會導致未定義的行為。如果互斥體未鎖定,則嘗試解鎖互斥體會導致未定義的行為。

返回值:
如果成功,pthread_mutex_lock()和 pthread_mutex_unlock() 函數返回零。否則,將返回一個錯誤號以指示錯誤。
如果獲取了互斥引用的互斥對象上的鎖,則函數 pthread_mutex_trylock() 返回零。否則,將返回一個錯誤號以指示錯誤。

如果出現以下情況,pthread_mutex_lock()和pthread_mutex_trylock()函數將失敗:

錯誤代碼含義
EINVAL互斥體是使用具有值PTHREAD_PRIO_PROTECT的協議屬性創建的,并且調用線程的優先級高于互斥體的當前優先級上限。
EBUSY無法獲取互斥體,因為它已被鎖定。
EINVAL互斥體指定的值不引用初始化的互斥體對象。
EAGAIN無法獲取互斥鎖,因為已超過互斥鎖的最大遞歸鎖數。
EDEADLK當前線程已擁有互斥體。
EPERM當前線程不擁有互斥體。

這些函數不會返回錯誤代碼EINTR。

pthread_mutex_unlock()

函數原型:

#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);

描述:
pthread_mutex_unlock() 函數釋放互斥引用的互斥對象。釋放互斥體的方式取決于互斥體的 type 屬性。如果在調用 pthread_mutex_unlock()時,互斥所引用的互斥對象上存在阻塞的線程,從而導致互斥體變為可用,則調度策略用于確定哪個線程應獲取互斥。(在PTHREAD_MUTEX_RECURSIVE互斥鎖的情況下,當計數達到零并且調用線程不再對此互斥鎖時,互斥鎖將變為可用)。

如果信號被傳遞到等待互斥體的線程,則在信號處理程序返回時,線程將恢復等待互斥體,就好像它沒有被中斷一樣。

返回值:
如果成功,返回零。否則,將返回一個錯誤號以指示錯誤。

示例代碼

#include <stdio.h> #include <pthread.h> #include <unistd.h>#define THREAD_SIZE 10#define ADD_MUTEX_LOCK 1#if ADD_MUTEX_LOCK pthread_mutex_t mutex; #endif// 10 * 100000 void *func(void *arg) {int *pcount = (int *)arg;int i = 0;while (i++ < 100000) { #if 0(*pcount)++; #elif ADD_MUTEX_LOCKpthread_mutex_lock(&mutex);(*pcount)++;pthread_mutex_unlock(&mutex); #endifusleep(1);}}int main(int argc, char **argv) {pthread_t threadid[THREAD_SIZE] = { 0 };#if ADD_MUTEX_LOCKpthread_mutex_init(&mutex, NULL); #endifint i = 0;int count = 0;for (i = 0; i < THREAD_SIZE; i++) {pthread_create(&threadid[i], NULL, func, &count);}// 1000w for (i = 0; i < 50; i++) {printf("count = %d\n", count);sleep(1);}return 0; }

上述代碼執行結果是1000000。也就是互斥鎖下的執行結果等于理論值。

自旋鎖

自旋鎖的接口和mutex類似。
函數原型:

#include <pthread.h> // 1. 銷毀自旋鎖 int pthread_spin_destroy(pthread_spinlock_t *lock); // 2. 初始化自旋鎖 int pthread_spin_init(pthread_spinlock_t *lock, int attr); // 3. 自旋鎖上鎖(阻塞) int pthread_spin_lock(pthread_spinlock_t *lock); // 4. 自旋鎖上鎖(非阻塞) int pthread_spin_trylock(pthread_spinlock_t *lock); // 5. 自旋鎖解鎖 int pthread_spin_unlock(pthread_spinlock_t *lock); 以上函數成功都返回0.

示例代碼

#include <stdio.h> #include <pthread.h> #include <unistd.h>#define THREAD_SIZE 10#define ADD_MUTEX_LOCK 0 #define ADD_SPIN_LOCK 1#if ADD_MUTEX_LOCK pthread_mutex_t mutex; #endif #if ADD_SPIN_LOCK pthread_spinlock_t spinlock; #endif// 10 * 100000 void *func(void *arg) {int *pcount = (int *)arg;int i = 0;while (i++ < 100000) { #if 0(*pcount)++; #elif ADD_MUTEX_LOCKpthread_mutex_lock(&mutex);(*pcount)++;pthread_mutex_unlock(&mutex); #elif ADD_SPIN_LOCKpthread_spin_lock(&spinlock);(*pcount)++;pthread_spin_unlock(&spinlock); #endifusleep(1);}}int main(int argc, char **argv) {pthread_t threadid[THREAD_SIZE] = { 0 };#if ADD_MUTEX_LOCKpthread_mutex_init(&mutex, NULL); #elif ADD_SPIN_LOCKpthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED); #endifint i = 0;int count = 0;for (i = 0; i < THREAD_SIZE; i++) {pthread_create(&threadid[i], NULL, func, &count);}// 1000w for (i = 0; i < 50; i++) {printf("count = %d\n", count);sleep(1);}return 0; }

上述代碼執行結果是1000000。也就是自旋鎖下的執行結果等于理論值。

互斥鎖與自旋鎖的區別

互斥鎖與自旋鎖的接口類似,但是底層實現有一定差異。
mutex在發現鎖已經被占用時,會讓出CPU資源,然后等待有解鎖時喚醒去搶鎖。
spin在發現鎖已經被占用時,會一直等著,直到搶到鎖。

死鎖

死鎖的兩種情況:
(1)如果兩個線程先后調用兩次lock,第二次調用lock時,由于鎖已被占用,該線程會掛起等待別的線程釋放鎖,然后鎖正是被自己占用著的,該線程又被掛起不能釋放鎖,因此就永遠處于掛起等待狀態了,進入死鎖。
(2)線程1和線程2。線程1獲得鎖1,線程2獲得鎖2,此時線程1調用lock企圖獲得鎖2,結果是需要掛起等待線程2釋放鎖2,而此時線程2也調用了lock企圖獲得鎖1,結果是線程2掛起等待線程1釋放鎖1,進入死鎖。

避免死鎖:
(1)共享資源操作前一定要獲得鎖。
(2)完成操作以后一定要釋放鎖。
(3)盡量短時間地占用鎖。
(4)有多鎖, 如獲得順序是abc連環扣, 釋放順序也應該是abc。
(5)線程錯誤返回時應該釋放它所獲得的鎖。
(6)寫程序是盡量避免同時獲得多個鎖。如果一定要這么做,所有線程在需要多個鎖時都按相同的先后順序獲得鎖,則不會出現死鎖。

原子操作

原子操作就是用一條指令解決問題;多條執行命令變為一條執行命令,使其不可分割。

CAS,全稱Compare And Swap。翻譯過來就是先比較再賦值,順序不可變;也就是先對比,如果值一致再賦值,如果不一致就不賦值。

常見的原子操作:
(1)加,add
(2)減,sub
(3)自增,inc
(4)自減,dec
(5)比較賦值,cas

注意,c語言的一條語句執行可能有副作用,但原子操作是沒有副作用的。
示例代碼:

#include <stdio.h> #include <pthread.h> #include <unistd.h>#define THREAD_SIZE 10#include <sys/time.h> #define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)// 原子操作 int inc(int *value,int add) {int old;__asm__ volatile("lock; xaddl %2, %1;": "=a" (old): "m" (*value),"a"(add): "cc","memory");return old; }// 10 * 1000000 void *func(void *arg) {int *pcount = (int *)arg;int i = 0;while (i++ < 1000000) {inc(pcount, 1);//usleep(1);} }int main(int argc, char **argv) {pthread_t threadid[THREAD_SIZE] = { 0 };// 統計執行時間struct timeval tv_start;gettimeofday(&tv_start, NULL);int i = 0;int count = 0;for (i = 0; i < THREAD_SIZE; i++) {pthread_create(&threadid[i], NULL, func, &count);}#if 0// 1000w for (i = 0; i < 50; i++) {printf("count = %d\n", count);sleep(1);} #elsefor (i = 0; i < THREAD_SIZE; i++) {pthread_join(threadid[i], NULL); //} #endifstruct timeval tv_end;gettimeofday(&tv_end, NULL);int time_used = TIME_SUB_MS(tv_end, tv_start);printf("time_used: %d\n", time_used);return 0; }

總結

對臨界資源操作時,常用原子操作和鎖。
鎖有互斥鎖、自旋鎖、讀寫鎖等,其他應用程序實現的業務鎖如悲觀鎖、樂觀鎖等。
在兩種情況下容易陷入死鎖:
(1)線程調用兩次lock,第一次已經獲得鎖,第二次發現鎖已占用進入等待,而鎖是被自己占用,進入無線等待的死鎖。
(2)多個線程多個鎖的情況,線程1獲得鎖1然后請求鎖2,線程2獲得鎖2然后請求鎖1,互相等待,進入鎖。

原子操作就是通過一條指令解決問題,封裝的CAS將多條執行命令變為一條執行命令,使其不可分割。

后言

本專欄知識點是通過<零聲教育>的系統學習,進行梳理總結寫下文章,對c/c++linux系統提升感興趣的讀者,可以點擊鏈接,詳細查看詳細的服務:C/C++服務器

總結

以上是生活随笔為你收集整理的Linux原子操作与锁实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。