#include <pthread.h>
gcc mainmutex2.c -o mutexrun -std=c99 -lpthread -D_GNU_SOURCE
宏定义-D_GNU_SOURCE能够允许程序访问一些GNU/Linux扩展函数,不添加会导致编译不通过。
在分析多线程的线程运行顺序时,可能会遇到明明觉得代码没有问题,可是线程运行的顺序就是与理论的不一致的情况,这时候有可能是多核处理器的原因,操作系统自动将多个线程分配到多个CPU中同时运行了,所以会导致像线程优先级设置等线程切换方法不起作用,所以在学习阶段就可以使用程序使所有线程都绑定到同一个CPU上运行,以便分析线程切换的逻辑。
一个绑核代码的实例如下:
int attach_cpu(int cpu_index){ int cpu_num = sysconf(_SC_NPROCESSORS_CONF); if (cpu_index < 0 || cpu_index >= cpu_num){ printf("cpu index ERROR!\n"); return -1; } cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(cpu_index, &mask); if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0){ printf("set affinity np ERROR!\n"); return -1; } return 0; } int main(void){ attach_cpu(0); //将主线程绑定到0核运行 }
首先,线程是怎么表示的呢?在Linux中线程就是通过数据类型pthread_t表示的,所有对线程的操作都需要指定一个线程,而pthread_t就可以指定一个唯一的线程。那pthread_t到底是什么呢?在/usr/include/bits/pthreadtypes.h有对pthread_t的定义:
/* Thread identifiers. The structure of the attribute type is not exposed on purpose. */ typedef unsigned long int pthread_t;
所以,其实pthread_t就是一个 long int 长整型变量,称之为线程id。
/* Create a new thread, starting with execution of START-ROUTINE getting passed ARG. Creation attributed come from ATTR. The new handle is stored in *NEWTHREAD. */ extern int pthread_create (pthread_t *__restrict __newthread, const pthread_attr_t *__restrict __attr, void *(*__start_routine) (void *), void *__restrict __arg);
参数 | 描述 |
---|---|
thread_t *__restrict __newthread | 线程句柄, 指向线程标识符指针 |
pthread_attr_t *attr | 线程属性,可以指定线程属性对象,也可以使用默认值NULL |
void * (*__start_routine)(void *) | 线程起始运行的函数地址,一旦线程被创建就会执行 |
void *__restrict __arg | 传递给线程运行函数的参数,需要使用void * 类型,多个参数可封装成结构体,如果没有参数传递,则使用 NULL |
返回值 int | 返回错误码,创建成功则返回0 |
/* Terminate calling thread. */ extern void pthread_exit (void *__retval)
pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
如果 main() 调用了 pthread_exit() ,那么在main线程终止时其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
/* Cancel THREAD immediately or at the next possibility. */ extern int pthread_cancel (pthread_t __th);
参数 | 描述 |
---|---|
pthread_t __th | 线程句柄, 指向线程标识符指针 |
pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。点此跳转到详细关于线程取消的知识,人家讲的蛮好的。
/* Make calling thread wait for termination of the thread TH. The exit status of the thread is stored in *THREAD_RETURN, if THREAD_RETURN is not NULL. This function is a cancellation point and therefore not marked with __THROW. */ extern int pthread_join (pthread_t __th, void **__thread_return); /* Indicate that the thread TH is never to be joined with PTHREAD_JOIN. The resources of TH will therefore be freed immediately when it terminates, instead of waiting for another thread to perform PTHREAD_JOIN on it. */ extern int pthread_detach (pthread_t __th) ;
参数 | 描述 |
---|---|
pthread_t __th | 线程句柄, 指向线程标识符指针 |
void **__thread_return | 用户定义的指针,用来存储被等待线程的返回值 |
当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。
A线程调用pthread_join(B)来连接B线程,pthread_join会阻碍A线程,直到B线程终止为止(也即线程同步,等待线程结束)。B线程终止后,A线程会回收B线程的资源。
A线程调用pthread_detach(B)来分离B线程,A线程与B线程分离,B线程结束后,资源由系统自动回收。
因此有以下四种回收线程资源避免僵尸线程的方法
方法1:创建线程前,利用pthread_attr_setdetachstate将线程设为detached,这样线程退出时,系统自动回收线程资源。
方法2:创建线程后,用pthread_detach将其设置为detached。
方法3:线程B退出后,线程A调用pthread_join来主动释放线程B的资源。pthread_join可能发生阻塞。
方法4:在线程任务中,pthread_detach(pthread_self())来分离自身线程;
前面提到,线程是具有自己的属性的,其属性由专门的属性对象pthread_attr_t 来描述。属性对象常用的相关的操作有初始化和销毁。
Linux中关于线程相关数据类型的定义均位于/usr/include/bits/pthreadtypes.h中。
typedef union { char __size[__SIZEOF_PTHREAD_MUTEXATTR_T]; int __align; } pthread_mutexattr_t;
/* Initialize thread attribute *ATTR with default attributes (detachstate is PTHREAD_JOINABLE, scheduling policy is SCHED_OTHER, no user-provided stack). */ extern int pthread_attr_init (pthread_attr_t *__attr); /* Destroy thread attribute *ATTR. */ extern int pthread_attr_destroy (pthread_attr_t *__attr) /* Get detach state attribute. */ extern int pthread_attr_getdetachstate (__const pthread_attr_t *__attr, int *__detachstate); /* Set detach state attribute. */ extern int pthread_attr_setdetachstate (pthread_attr_t *__attr, int __detachstate)
Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性,对每个属性都有相关的函数进行操作,具体可以点此跳转到详细关于线程属性的讲解。
创建两个线程,都对变量count执行 +1 操作,直到count等于10。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> int count = 0; void thread_func1(){ attach_cpu(0); // 绑定此线程到0核运行,函数实现见本博客第一节第3小节 while(count < 10){ printf("thread1: %d\n",count); count++; sleep(1); } } void thread_func2(){ attach_cpu(0); // 绑定此线程到0核运行 while(count < 10){ printf("thread2: %d\n",count); count++; sleep(1); } } int main(){ int res = 0 pthread_t thread1, thread2; /* 创建线程 */ res = pthread_create(&thread1, NULL, (void*)thread_func1, NULL); if(0 != res){ printf("create thread 1 error ! errNo : %d\n", res); exit(1); } res = pthread_create(&thread2, NULL, (void*)thread_func2, NULL); if(0 != res){ printf("create thread 2 error ! errNo : %d\n", res); exit(1); } /* 连接线程,等待线程结束 */ res = pthread_join(thread1, NULL); if (0 != res){ printf("thread1 pthread_join error, errorNo: %d\n", res); return 0; } res = pthread_join(thread2, NULL); if (0 != res){ printf("thread2 pthread_join error, errorNo: %d\n", res); return 0; } return 0; }
参考资料
线程属性: https://www.cnblogs.com/zengkefu/p/5683957.html(墙裂推荐)
线程取消:https://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html
线程连接和线程分离:
https://zhuanlan.zhihu.com/p/97418361
https://www.runoob.com/cplusplus/cpp-multithreading.html
避免僵尸线程的四种方法:
https://blog.csdn.net/bobbypollo/article/details/79891451