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()的返回。這將導致進程中所有線程的終止。
參數介紹:
| attr | attr參數指向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_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() */ }無原子操作
在多個線程中,對一個變量不斷操作,如果沒有原子操作會怎么樣?
示例代碼:
上述代碼執行結果理論上是1000000,但是最后結果是994656。也就是無原子操作下的執行結果小于理論值。
原因在于,執行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 <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語言的一條語句執行可能有副作用,但原子操作是沒有副作用的。
示例代碼:
總結
對臨界資源操作時,常用原子操作和鎖。
鎖有互斥鎖、自旋鎖、讀寫鎖等,其他應用程序實現的業務鎖如悲觀鎖、樂觀鎖等。
在兩種情況下容易陷入死鎖:
(1)線程調用兩次lock,第一次已經獲得鎖,第二次發現鎖已占用進入等待,而鎖是被自己占用,進入無線等待的死鎖。
(2)多個線程多個鎖的情況,線程1獲得鎖1然后請求鎖2,線程2獲得鎖2然后請求鎖1,互相等待,進入鎖。
原子操作就是通過一條指令解決問題,封裝的CAS將多條執行命令變為一條執行命令,使其不可分割。
后言
本專欄知識點是通過<零聲教育>的系統學習,進行梳理總結寫下文章,對c/c++linux系統提升感興趣的讀者,可以點擊鏈接,詳細查看詳細的服務:C/C++服務器
總結
以上是生活随笔為你收集整理的Linux原子操作与锁实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 牛客网sql练习打卡
- 下一篇: linux 其他常用命令