线程的创建

原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数:

  thread:线程ID存放空间地址

  attr:线程属性,默认填NULL

  start_routine:线程函数指针

  arg:给线程函数传递的参数

测试例程:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 
 4 void * pthread_func (void *p_arg)
 5 {
 6     while (1) {
 7         printf("pthread arg = %d\n", (int)p_arg);
 8 
 9         sleep(5);
10     }
11 
12     return NULL;
13 }
14 
15 int main(int argc, const char *argv[])
16 {
17     pthread_t id;
18 
19     pthread_create(&id, NULL, pthread_func, (void *)6);
20 
21     while (1) {
22         printf("main\n");
23 
24         sleep(2);
25     }
26 
27     return 0;
28 }

编译时输入:gcc main.c -lpthread

探究是否先创建的线程会先执行

  如果创建了很多个线程,这些线程是怎么开始执行的呢,为此我写了如下代码:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 /* 定义最大线程数 */
 5 #define THREAD_CNT 100
 6 
 7 void * func(void *p_arg)
 8 {
 9     printf("%d ", (int)p_arg);
10 }
11 
12 int main(int argc, const char *argv[])
13 {
14     int i;
15     pthread_t tid[THREAD_CNT];
16     
17     for(i = 0; i < THREAD_CNT; i++)
18     {
19         pthread_create(&tid[i], NULL, func, (void *)i);
20     }
21     
22     sleep(1);
23  
24     return 0;
25 }

三次测试结果如下:

 

 从结果上可以看出,并非先创建的线程会先执行,从结果上看不出规律

线程退出后资源占用情况探究

  如果说创建的线程不会退出,那就不会有这个问题,在下列几篇博客的启发下,我决定亲自来探究一下这个问题,但是使用的方法和工具也许会和他们有所区别。

 

  多线程之pthread_create()函数https://blog.csdn.net/wushuomin/article/details/80051295

 

  解决使用pthread_create函数造成的内存泄露:https://blog.csdn.net/qiurisuixiang/article/details/6648213

 

  linux中pthread_join()与pthread_detach()详解:https://blog.csdn.net/weibo1230123/article/details/81410241

  探究的具体问题为,线程的分离属性与非分离属性在线程退出后的资源回收情况,以及调用不同的函数对结果的影响。

  编写测试程序:

main.c

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <pthread.h>
 4 
 5 /* 定义最大线程数 */
 6 #define THREAD_CNT 5
 7 
 8 void * func(void *p_arg)
 9 {
10     //pthread_detach(pthread_self());
11     printf("%d ", (int)p_arg);
12 }
13 
14 int main(int argc, const char *argv[])
15 {
16     int i;
17     pthread_t tid[THREAD_CNT];
18     //pthread_attr_t attr;
19     
20     //pthread_attr_init(&attr);
21     //pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
22     //pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
23     
24     for(i = 0; i < THREAD_CNT; i++) {
25         pthread_create(&tid[i], NULL, func, (void *)i);
26         //pthread_join(tid[i], NULL);
27         //sleep(1);    
28     }
29 
30 //    sleep(1);
31 
32     return 0;
33 }

各种情况的验证都将在这份代码上进行改动。

首先用gcc main.c -lpthread生成a.out,然后执行valgrind ./a.out命令(如果没有valgrind需要先安装valgrind),结果如下:

 

 这样可能发生内存泄漏,猜测可能是因为主进程退出而线程还未来得及退出导致的,因此放开代码第30行的注释,让线程有时间去退出,但是执行结果是一样的。

放开第26行的注释,每执行一个线程之后都用pthread_join回收,运行结果为 没有泄漏的可能,如下:

 

 为了更直观的观察系统资源使用情况,将第6行THREAD_CNT的值改到1000,注释掉第26行,并放开第27行,如下:

 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <pthread.h>
 4 
 5 /* 定义最大线程数 */
 6 #define THREAD_CNT 1000
 7 
 8 void * func(void *p_arg)
 9 {
10     //pthread_detach(pthread_self());
11     printf("%d \n", (int)p_arg);  //这里不加换行符的话数字打印不出来
12 }
13 
14 int main(int argc, const char *argv[])
15 {
16     int i;
17     pthread_t tid[THREAD_CNT];
18     //pthread_attr_t attr;
19     
20     //pthread_attr_init(&attr);
21     //pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
22     //pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
23     
24     for(i = 0; i < THREAD_CNT; i++) {
25         pthread_create(&tid[i], NULL, func, (void *)i);
26         //pthread_join(tid[i], NULL);
27         sleep(1);    
28     }
29 
30     sleep(1);
31 
32     return 0;
33 }

编译成功后直接运行a.out,然后另开一个终端,每隔一段时间运行ps -aux命令查看进程状态,并记录结果:

 

 

 

注意到名为"a.out"的进程,%MEM,VSZ和RSS一栏上的数字越来越大(%MEM表示内存使用率,RSS表示固定内存量,VSZ表示虚拟内存量)。

放开第10行的注释:pthread_detach(pthread_self());  或者 放开第26行的注释:pthread_join(tid[i], NULL); 或者 将代码改成如下样子:

 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <pthread.h>
 4 
 5 /* 定义最大线程数 */
 6 #define THREAD_CNT 1000
 7 
 8 void * func(void *p_arg)
 9 {
10     //pthread_detach(pthread_self());
11     printf("%d \n", (int)p_arg);
12 }
13 
14 int main(int argc, const char *argv[])
15 {
16     int i;
17     pthread_t tid[THREAD_CNT];
18     pthread_attr_t attr;
19     
20     pthread_attr_init(&attr);
21     //pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
22     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
23     
24     for(i = 0; i < THREAD_CNT; i++) {
25         pthread_create(&tid[i], &attr, func, (void *)i);
26         //pthread_join(tid[i], NULL);
27         sleep(1);
28     }
29 
30     sleep(1);
31 
32     return 0;
33 }

 

这三种改动方式运行的程序占用的资源大小都不会变大,表明在线程结束之后资源确实回收了。

结论:

为防止线程退出时没有释放资源造成资源浪费,可以用三种方式防止资源浪费:

1、在线程函数中调用pthread_detach(pthread_self()); 让自己变为分离线程;

2、在创建线程的时候传入分离属性,调用pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 让属性变为分离属性。PTHREAD_CREATE_DETACHED的值其实就是1,PTHREAD_CREATE_JOINABLE的值为0(需包含pthread.h)。

3、使用pthread_join回收资源。

需要注意的是pthread_join会阻塞等待线程结束,而如果传入的线程是分离属性,那么pthread_join函数会立即返回EINVAL(值为22,需包含errno.h)。

 

另外,对于https://blog.csdn.net/qiurisuixiang/article/details/6648213这篇博客中提到的这个问题:

 

 

我进行了验证,使用的工具为valgrind。确实如果通过给线程设置分离属性的方法去回收资源,确实有可能会报“可能的泄漏”,而如果使用pthread_join则不会。但就我个人而言并不喜欢使用pthread_join来回收内存,原因是pthread_join会造成阻塞,这就需要考虑pthread_join调用的时机。并且我已经用ps -aux证实了给线程设置分离属性不会造成泄漏,因此valgrind它报可能泄漏就让它报去吧。