Linux原子操作与锁实现
原子操作CAS與鎖
- 多線程的使用
- 線程的創(chuàng)建
- 線程的終止
- pthread_exit()
- pthread_cancel()
- 線程的等待
- 線程的屬性
- 無(wú)原子操作
- 互斥鎖
- pthread_mutex_init()
- pthread_mutex_destroy()
- pthread_mutex_lock()和pthread_mutex_trylock()
- pthread_mutex_unlock()
- 示例代碼
- 自旋鎖
- 示例代碼
- 互斥鎖與自旋鎖的區(qū)別
- 死鎖
- 原子操作
- 總結(jié)
- 后言
多線程的使用
線程的創(chuàng)建
函數(shù)原型:
#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()函數(shù)在調(diào)用進(jìn)程中啟動(dòng)一個(gè)新線程。新線程通過(guò)調(diào)用start_routine()開始執(zhí)行;arg作為start_routine()的唯一參數(shù)傳遞。
新線程以以下方式之一終止:
(1)它調(diào)用pthread_exit(),指定一個(gè)退出狀態(tài)值,該值可用于調(diào)用pthrread_join()的同一進(jìn)程中的另一個(gè)線程,即pthrread_join()可以接收pthread_exit()返回的值。
(2)它從start_routine()返回。這相當(dāng)于使用return語(yǔ)句中提供的值調(diào)用pthread_exit()。
(3)它被pthread_cancel()取消。
(4)進(jìn)程中的任何線程都調(diào)用exit(),或者主線程執(zhí)行main()的返回。這將導(dǎo)致進(jìn)程中所有線程的終止。
參數(shù)介紹:
| attr | attr參數(shù)指向pthread_attr_t結(jié)構(gòu),其內(nèi)容在線程創(chuàng)建時(shí)用于確定新線程的屬性;使用pthread_attr_init()和相關(guān)函數(shù)初始化該結(jié)構(gòu)。如果attr為空,則使用默認(rèn)屬性創(chuàng)建線程。 |
| thread | 在返回之前,成功調(diào)用pthread_create()將新線程的ID存儲(chǔ)在thread指向的緩沖區(qū)中;此標(biāo)識(shí)符用于在后續(xù)調(diào)用其他pthreads函數(shù)時(shí)引用線程。 |
| start_routine | 線程入口函數(shù) |
| arg | 線程入口函數(shù)的參數(shù) |
返回值:
成功時(shí),返回0;出錯(cuò)時(shí),它返回一個(gè)錯(cuò)誤號(hào),并且*thread的內(nèi)容未定義。
錯(cuò)誤號(hào):
| EAGAIN | 資源不足,無(wú)法創(chuàng)建另一個(gè)線程。 |
| AGAIN A | 遇到系統(tǒng)對(duì)線程數(shù)量施加的限制。可能觸發(fā)此錯(cuò)誤的限制有很多:已達(dá)到RLIMIT_NPROC軟資源限制【通過(guò)setrlimit()設(shè)置】,該限制限制了真實(shí)用戶ID的進(jìn)程和線程數(shù);已達(dá)到內(nèi)核對(duì)進(jìn)程和線程數(shù)的系統(tǒng)范圍限制,即/proc/sys/kernel/threads max【請(qǐng)參閱proc()】;或者達(dá)到最大pid數(shù)/proc/sys/kernel/pid_max【見(jiàn)proc()】。 |
| EINVAL | 屬性中的設(shè)置無(wú)效。 |
| EPERM | 沒(méi)有設(shè)置attr中指定的調(diào)度策略和參數(shù)的權(quán)限。 |
其他:
新線程繼承創(chuàng)建線程的信號(hào)掩碼【pthread_sigmask()】的副本。新線程的掛起信號(hào)集為空【sigpending()】。新線程不繼承創(chuàng)建線程的備用信號(hào)堆棧【sigaltstack()】。
新線程的CPU時(shí)間時(shí)鐘的初始值為0【參見(jiàn)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)它調(diào)用pthread_exit(),指定一個(gè)退出狀態(tài)值,該值可用于調(diào)用pthrread_join()的同一進(jìn)程中的另一個(gè)線程,即pthrread_join()可以接收pthread_exit()返回的值。
(2)它從start_routine()返回。這相當(dāng)于使用return語(yǔ)句中提供的值調(diào)用pthread_exit()。
(3)它被pthread_cancel()取消。
(4)進(jìn)程中的任何線程都調(diào)用exit(),或者主線程執(zhí)行main()的返回。這將導(dǎo)致進(jìn)程中所有線程的終止。
pthread_exit()
函數(shù)原型:
#include <pthread.h>void pthread_exit(void *retval);// Compile and link with -pthread.描述:
(1)pthread_exit()函數(shù)終止調(diào)用線程并通過(guò)retval返回一個(gè)值,該值(如果線程是可連接的)可用于調(diào)用pthrea_join()的同一進(jìn)程中的另一個(gè)線程,即可被pthrea_join()接收返回值。
(2)任何由pthread_cleanup_push()建立的尚未彈出的清理處理程序都會(huì)彈出(與它們被推送的順序相反)并執(zhí)行。如果線程具有任何特定于線程的數(shù)據(jù),則在執(zhí)行清理處理程序后,將以未指定的順序調(diào)用相應(yīng)的析構(gòu)函數(shù)。
(3)當(dāng)線程終止時(shí),進(jìn)程共享資源(例如互斥體、條件變量、信號(hào)量和文件描述符)不會(huì)被釋放,使用atexit()注冊(cè)的函數(shù)也不會(huì)被調(diào)用。
(4)進(jìn)程中的最后一個(gè)線程終止后,進(jìn)程通過(guò)調(diào)用exit()終止,退出狀態(tài)為零;因此,釋放進(jìn)程共享資源并調(diào)用使用atexit()注冊(cè)的函數(shù)。
返回值:
此函數(shù)不返回調(diào)用方。
錯(cuò)誤:
此函數(shù)始終成功。
注意:
(1)從除主線程之外的任何線程的start函數(shù)執(zhí)行返回將導(dǎo)致隱式調(diào)用pthread_exit(),使用函數(shù)的返回值作為線程的退出狀態(tài)。
(2)為了允許其他線程繼續(xù)執(zhí)行,主線程應(yīng)該通過(guò)調(diào)用pthread_exit()而不是exit()來(lái)終止。
(3)retval指向的值不應(yīng)位于調(diào)用線程的堆棧上,因?yàn)樵摱褩5膬?nèi)容在線程終止后未定義。
pthread_cancel()
函數(shù)原型:
#include <pthread.h>int pthread_cancel(pthread_t thread);// Compile and link with -pthread.描述:
pthread_cancel()函數(shù)向線程thread發(fā)送取消請(qǐng)求。目標(biāo)線程是否以及何時(shí)響應(yīng)取消請(qǐng)求取決于該線程控制的兩個(gè)屬性:其可取消性state和type。
由pthread_setcancelstate()設(shè)置線程的可取消狀態(tài)可以啟用(新線程的默認(rèn)狀態(tài))或禁用。如果線程已禁用取消,則取消請(qǐng)求將保持排隊(duì)狀態(tài),直到線程啟用取消。如果線程已啟用取消,則其可取消性類型決定何時(shí)取消。
由pthread_setcanceltype()確定的線程的取消類型可以是異步的或延遲的(新線程的默認(rèn)值)。異步可取消性意味著線程可以隨時(shí)取消(通常是立即取消,但系統(tǒng)不保證)。延遲可取消性意味著取消將被延遲,直到線程下一次調(diào)用作為取消點(diǎn)的函數(shù)。pthreads()中提供了作為或可能是取消點(diǎn)的函數(shù)列表。
執(zhí)行取消請(qǐng)求時(shí),線程將執(zhí)行以下步驟(按順序):
#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()取消清理處理程序被彈出(與它們被推送的順序相反)并被調(diào)用。參見(jiàn)pthread_cleanup_push()以未指定的順序調(diào)用線程特定的數(shù)據(jù)析構(gòu)函數(shù)。參見(jiàn)pthread_key_create()終止線程。參見(jiàn)pthread_exit()上述步驟相對(duì)于pthread_cancel()調(diào)用異步發(fā)生;pthread_cancel()的返回狀態(tài)僅通知調(diào)用方取消請(qǐng)求是否已成功排隊(duì)。
被取消的線程終止后,使用pthread_join()與該線程的連接將獲得pthrea_canceled作為線程的退出狀態(tài)。(使用線程連接是知道取消已完成的唯一方法。)
返回值:
成功時(shí),返回0;出錯(cuò)時(shí),返回非零錯(cuò)誤號(hào)。
錯(cuò)誤:
ESRCH,找不到ID為thread的線程。
線程的等待
函數(shù)原型:
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);// Compile and link with -pthread.描述:
pthread_join()函數(shù)等待線程指定的線程終止。如果該線程已經(jīng)終止,則pthread_join()立即返回。thread指定的線程必須是可連接的。
如果retval不為空,則pthread_join()將目標(biāo)線程的退出狀態(tài)(即,目標(biāo)線程提供給pthrea_exit()的值)復(fù)制到retval所指向的位置。如果目標(biāo)線程被取消,則PTHREAD_CANCELED被置于retval中。
如果多個(gè)線程同時(shí)嘗試與同一線程聯(lián)接,則結(jié)果是未定義的。如果調(diào)用pthread_join()的線程被取消,那么目標(biāo)線程將保持可連接狀態(tài)(即,它不會(huì)被分離)。
返回值:
成功時(shí),返回0;出錯(cuò)時(shí),它返回錯(cuò)誤號(hào)。
錯(cuò)誤號(hào):
| EDEADLK | 檢測(cè)到死鎖(例如,兩個(gè)線程試圖彼此連接);或thread指定調(diào)用線程。 |
| EINVAL | 線程不是可連接線程。 |
| EINVAL | 另一個(gè)線程已在等待加入此線程。 |
| ESRCH | 找不到ID為線程的線程。 |
線程的屬性
函數(shù)原型:
#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()函數(shù)使用默認(rèn)屬性值初始化attr指向的線程屬性對(duì)象。在這個(gè)調(diào)用之后,可以使用各種相關(guān)函數(shù)(下方列出)設(shè)置對(duì)象的各個(gè)屬性,然后可以在創(chuàng)建線程的一個(gè)或多個(gè)pthread_create()調(diào)用中使用該對(duì)象。
對(duì)已初始化的線程屬性對(duì)象調(diào)用pthread_attr_init()會(huì)導(dǎo)致未定義的行為。
當(dāng)不再需要線程屬性對(duì)象時(shí),應(yīng)使用pthread_attr_destroy()函數(shù)將其銷毀。 銷毀線程屬性對(duì)象對(duì)使用該對(duì)象創(chuàng)建的線程沒(méi)有影響。
線程屬性對(duì)象被銷毀后,可以使用pthread_attr_init()對(duì)其重新初始化。任何其他使用已銷毀線程屬性對(duì)象的方法都會(huì)產(chǎn)生未定義的結(jié)果。
返回值:
成功時(shí),這些函數(shù)返回0;出錯(cuò)時(shí),它們返回一個(gè)非零錯(cuò)誤號(hào)。
錯(cuò)誤:
在Linux上,這些函數(shù)總是成功的(但可移植和未來(lái)驗(yàn)證的應(yīng)用程序應(yīng)該處理可能的錯(cuò)誤返回)。
pthread_attr_t類型應(yīng)被視為不透明的:除通過(guò)pthreads函數(shù)外,對(duì)對(duì)象的任何訪問(wèn)都是不可移植的,并產(chǎn)生未定義的結(jié)果。
示例代碼:
#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() */ }無(wú)原子操作
在多個(gè)線程中,對(duì)一個(gè)變量不斷操作,如果沒(méi)有原子操作會(huì)怎么樣?
示例代碼:
上述代碼執(zhí)行結(jié)果理論上是1000000,但是最后結(jié)果是994656。也就是無(wú)原子操作下的執(zhí)行結(jié)果小于理論值。
原因在于,執(zhí)行idx++時(shí)匯編代碼是:
也就是c語(yǔ)言是一條語(yǔ)句,但真正執(zhí)行時(shí)是三條命令。在無(wú)原子操作時(shí),就可能出現(xiàn)如下情況:
#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原意要自增兩次,然而實(shí)際只自增了一次,因此無(wú)原子操作下的執(zhí)行結(jié)果小于理論值。
互斥鎖
讓臨界資源只允許在一個(gè)線程中執(zhí)行。
pthread_mutex_init()
函數(shù)原型:
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);函數(shù)描述:
互斥鎖的初始化。
pthread_mutex_init() 函數(shù)是以動(dòng)態(tài)方式創(chuàng)建互斥鎖的,參數(shù)attr指定了新建互斥鎖的屬性。如果參數(shù)attr為空(NULL),則使用默認(rèn)的互斥鎖屬性,默認(rèn)屬性為快速互斥鎖 。
互斥鎖的屬性在創(chuàng)建鎖的時(shí)候指定,在實(shí)現(xiàn)中僅有一個(gè)鎖類型屬性,不同的鎖類型在試圖對(duì)一個(gè)已經(jīng)被鎖定的互斥鎖加鎖時(shí)表現(xiàn)不同。
返回:
成功會(huì)返回零,其他任何返回值都表示出現(xiàn)了錯(cuò)誤。
成功后,互斥鎖被初始化為未鎖住態(tài)。
pthread_mutex_destroy()
用于注銷一個(gè)互斥鎖,函數(shù)原型:
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex)銷毀一個(gè)互斥鎖即意味著釋放它所占用的資源,且要求鎖當(dāng)前處于開放狀態(tài)。由于在Linux中,互斥鎖并不占用任何資源,因此pthread_mutex_destroy()僅僅檢查鎖狀態(tài)(鎖定狀態(tài)則返回EBUSY)。
pthread_mutex_lock()和pthread_mutex_trylock()
函數(shù)原型:
#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex);描述:
互斥引用的互斥對(duì)象通過(guò)調(diào)用 pthread_mutex_lock()被鎖定。如果互斥鎖已被鎖定,則調(diào)用線程將阻塞,直到互斥體變?yōu)榭捎谩4瞬僮鲗⒎祷赜商幱阪i定狀態(tài)的互斥所引用的互斥對(duì)象,其中調(diào)用線程是其所有者。
函數(shù) pthread_mutex_trylock()與 pthread_mutex_lock()相同,只是如果互斥引用的互斥對(duì)象當(dāng)前被鎖定(由任何線程,包括當(dāng)前線程鎖定),則調(diào)用將立即返回。
| PTHREAD_MUTEX_NORMAL | 不提供死鎖檢測(cè)。嘗試重新鎖定互斥鎖會(huì)導(dǎo)致死鎖。如果線程嘗試解鎖它尚未鎖定的互斥鎖或已解鎖的互斥體,則會(huì)導(dǎo)致未定義的行為。 |
| PTHREAD_MUTEX_ERRORCHECK | 提供錯(cuò)誤檢查。如果線程嘗試重新鎖定已鎖定的互斥鎖,則會(huì)返回錯(cuò)誤。如果線程嘗試解鎖尚未鎖定的互斥體或已解鎖的互斥體,則將返回錯(cuò)誤。 |
| PTHREAD_MUTEX_RECURSIVE | 互斥鎖將保留鎖定計(jì)數(shù)的概念。當(dāng)線程首次成功獲取互斥鎖時(shí),鎖定計(jì)數(shù)將設(shè)置為 1。每次線程重新鎖定此互斥鎖時(shí),鎖定計(jì)數(shù)都會(huì)遞增 1。每次線程解鎖互斥體時(shí),鎖定計(jì)數(shù)都會(huì)減少 1。當(dāng)鎖定計(jì)數(shù)達(dá)到零時(shí),互斥鎖將可供其他線程獲取。如果線程嘗試解鎖尚未鎖定的互斥體或已解鎖的互斥體,則將返回錯(cuò)誤。 |
| PTHREAD_MUTEX_DEFAULT | 嘗試遞歸鎖定互斥會(huì)導(dǎo)致未定義的行為。如果互斥體未被調(diào)用線程鎖定,則嘗試解鎖該互斥體會(huì)導(dǎo)致未定義的行為。如果互斥體未鎖定,則嘗試解鎖互斥體會(huì)導(dǎo)致未定義的行為。 |
返回值:
如果成功,pthread_mutex_lock()和 pthread_mutex_unlock() 函數(shù)返回零。否則,將返回一個(gè)錯(cuò)誤號(hào)以指示錯(cuò)誤。
如果獲取了互斥引用的互斥對(duì)象上的鎖,則函數(shù) pthread_mutex_trylock() 返回零。否則,將返回一個(gè)錯(cuò)誤號(hào)以指示錯(cuò)誤。
如果出現(xiàn)以下情況,pthread_mutex_lock()和pthread_mutex_trylock()函數(shù)將失敗:
| EINVAL | 互斥體是使用具有值PTHREAD_PRIO_PROTECT的協(xié)議屬性創(chuàng)建的,并且調(diào)用線程的優(yōu)先級(jí)高于互斥體的當(dāng)前優(yōu)先級(jí)上限。 |
| EBUSY | 無(wú)法獲取互斥體,因?yàn)樗驯绘i定。 |
| EINVAL | 互斥體指定的值不引用初始化的互斥體對(duì)象。 |
| EAGAIN | 無(wú)法獲取互斥鎖,因?yàn)橐殉^(guò)互斥鎖的最大遞歸鎖數(shù)。 |
| EDEADLK | 當(dāng)前線程已擁有互斥體。 |
| EPERM | 當(dāng)前線程不擁有互斥體。 |
這些函數(shù)不會(huì)返回錯(cuò)誤代碼EINTR。
pthread_mutex_unlock()
函數(shù)原型:
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);描述:
pthread_mutex_unlock() 函數(shù)釋放互斥引用的互斥對(duì)象。釋放互斥體的方式取決于互斥體的 type 屬性。如果在調(diào)用 pthread_mutex_unlock()時(shí),互斥所引用的互斥對(duì)象上存在阻塞的線程,從而導(dǎo)致互斥體變?yōu)榭捎?#xff0c;則調(diào)度策略用于確定哪個(gè)線程應(yīng)獲取互斥。(在PTHREAD_MUTEX_RECURSIVE互斥鎖的情況下,當(dāng)計(jì)數(shù)達(dá)到零并且調(diào)用線程不再對(duì)此互斥鎖時(shí),互斥鎖將變?yōu)榭捎?#xff09;。
如果信號(hào)被傳遞到等待互斥體的線程,則在信號(hào)處理程序返回時(shí),線程將恢復(fù)等待互斥體,就好像它沒(méi)有被中斷一樣。
返回值:
如果成功,返回零。否則,將返回一個(gè)錯(cuò)誤號(hào)以指示錯(cuò)誤。
示例代碼
#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; }上述代碼執(zhí)行結(jié)果是1000000。也就是互斥鎖下的執(zhí)行結(jié)果等于理論值。
自旋鎖
自旋鎖的接口和mutex類似。
函數(shù)原型:
示例代碼
#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; }上述代碼執(zhí)行結(jié)果是1000000。也就是自旋鎖下的執(zhí)行結(jié)果等于理論值。
互斥鎖與自旋鎖的區(qū)別
互斥鎖與自旋鎖的接口類似,但是底層實(shí)現(xiàn)有一定差異。
mutex在發(fā)現(xiàn)鎖已經(jīng)被占用時(shí),會(huì)讓出CPU資源,然后等待有解鎖時(shí)喚醒去搶鎖。
spin在發(fā)現(xiàn)鎖已經(jīng)被占用時(shí),會(huì)一直等著,直到搶到鎖。
死鎖
死鎖的兩種情況:
(1)如果兩個(gè)線程先后調(diào)用兩次lock,第二次調(diào)用lock時(shí),由于鎖已被占用,該線程會(huì)掛起等待別的線程釋放鎖,然后鎖正是被自己占用著的,該線程又被掛起不能釋放鎖,因此就永遠(yuǎn)處于掛起等待狀態(tài)了,進(jìn)入死鎖。
(2)線程1和線程2。線程1獲得鎖1,線程2獲得鎖2,此時(shí)線程1調(diào)用lock企圖獲得鎖2,結(jié)果是需要掛起等待線程2釋放鎖2,而此時(shí)線程2也調(diào)用了lock企圖獲得鎖1,結(jié)果是線程2掛起等待線程1釋放鎖1,進(jìn)入死鎖。
避免死鎖:
(1)共享資源操作前一定要獲得鎖。
(2)完成操作以后一定要釋放鎖。
(3)盡量短時(shí)間地占用鎖。
(4)有多鎖, 如獲得順序是abc連環(huán)扣, 釋放順序也應(yīng)該是abc。
(5)線程錯(cuò)誤返回時(shí)應(yīng)該釋放它所獲得的鎖。
(6)寫程序是盡量避免同時(shí)獲得多個(gè)鎖。如果一定要這么做,所有線程在需要多個(gè)鎖時(shí)都按相同的先后順序獲得鎖,則不會(huì)出現(xiàn)死鎖。
原子操作
原子操作就是用一條指令解決問(wèn)題;多條執(zhí)行命令變?yōu)橐粭l執(zhí)行命令,使其不可分割。
CAS,全稱Compare And Swap。翻譯過(guò)來(lái)就是先比較再賦值,順序不可變;也就是先對(duì)比,如果值一致再賦值,如果不一致就不賦值。
常見(jiàn)的原子操作:
(1)加,add
(2)減,sub
(3)自增,inc
(4)自減,dec
(5)比較賦值,cas
注意,c語(yǔ)言的一條語(yǔ)句執(zhí)行可能有副作用,但原子操作是沒(méi)有副作用的。
示例代碼:
總結(jié)
對(duì)臨界資源操作時(shí),常用原子操作和鎖。
鎖有互斥鎖、自旋鎖、讀寫鎖等,其他應(yīng)用程序?qū)崿F(xiàn)的業(yè)務(wù)鎖如悲觀鎖、樂(lè)觀鎖等。
在兩種情況下容易陷入死鎖:
(1)線程調(diào)用兩次lock,第一次已經(jīng)獲得鎖,第二次發(fā)現(xiàn)鎖已占用進(jìn)入等待,而鎖是被自己占用,進(jìn)入無(wú)線等待的死鎖。
(2)多個(gè)線程多個(gè)鎖的情況,線程1獲得鎖1然后請(qǐng)求鎖2,線程2獲得鎖2然后請(qǐng)求鎖1,互相等待,進(jìn)入鎖。
原子操作就是通過(guò)一條指令解決問(wèn)題,封裝的CAS將多條執(zhí)行命令變?yōu)橐粭l執(zhí)行命令,使其不可分割。
后言
本專欄知識(shí)點(diǎn)是通過(guò)<零聲教育>的系統(tǒng)學(xué)習(xí),進(jìn)行梳理總結(jié)寫下文章,對(duì)c/c++linux系統(tǒng)提升感興趣的讀者,可以點(diǎn)擊鏈接,詳細(xì)查看詳細(xì)的服務(wù):C/C++服務(wù)器
總結(jié)
以上是生活随笔為你收集整理的Linux原子操作与锁实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 牛客网sql练习打卡
- 下一篇: 数学建模之倚天剑与屠龙刀