#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()的返回这将导致进程中所有线程的终止参数介绍:#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;}intmain(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 into corresponding 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 no longer 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.2、线程的终止新线程以以下方式之一终止:(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()中提供了作为或可能是取消点的函数列表执行取消请求时,线程将执行以下步骤(按顺序):上述步骤相对于pthread_cancel()调用异步发生;pthread_cancel()的返回状态仅通知调用方取消请求是否已成功排队被取消的线程终止后,使用pthread_join()与该线程的连接将获得pthrea_canceled作为线程的退出状态(使用线程连接是知道取消已完成的唯一方法)返回值:成功时,返回0;出错时,返回非零错误号错误:ESRCH,找不到ID为thread的线程1.3、线程的等待函数原型:#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;出错时,它返回错误号错误号:#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 voiddisplay_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 that retrieves the attributes of the thread specified in its first 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 /}intmain(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 the stack-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_SIZE10// 10 100000void 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], %eaxInc %eaxMov %eax,[idx]
也就是c语言是一条语句,但真正执行时是三条命令在无原子操作时,就可能出现如下情况:原意要自增两次,然而实际只自增了一次,因此无原子操作下的执行结果小于理论值三、互斥锁让临界资源只允许在一个线程中执行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()相同,只是如果互斥引用的互斥对象当前被锁定(由任何线程,包括当前线程锁定),则调用将立即返回#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_SIZE10#define ADD_MUTEX_LOCK1#if ADD_MUTEX_LOCKpthread_mutex_t mutex;#endif// 10 100000void 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_SIZE10#define ADD_MUTEX_LOCK0#define ADD_SPIN_LOCK1#if ADD_MUTEX_LOCKpthread_mutex_t mutex;#endif#if ADD_SPIN_LOCKpthread_spinlock_t spinlock;#endif// 10 100000void 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_SIZE10#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 1000000void 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将多条执行命令变为一条执行命令,使其不可分割关注#华为云开发者联盟# 点击下方,第一时间了解华为云新鲜技术~华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云(图片来源网络,侵删)
0 评论