当前位置:   article > 正文

Linux下多线程编程思考与学习----01(线程创建pthread_create函数详解)_linux pthread_create

linux pthread_create

一、为什么需要使用多线程编程?

        当在执行某些程序的时候难免会需要同时执行两个、甚至多个任务,当然可以使用多个进程进行执行,但是难免需要用到信息的传输,因此就需要引入进程间通信的问题,这对于CPU内存调度的压力也会更大。多线程编程的优点是在同一个进程下,多个线程可以访问访问同一个全局变量,这使得多个线程之间的沟通交互更加便捷,对CPU资源消耗也会越少。(在Linux系统中,调度是以线程为单位的;但是资源的分配是以进程为单位的)

二、如何使用:

        1、如何创建线程?

  1. #include <pthread.h>
  2. int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
  3. RETURN VALUE
  4. On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.

第一个参数用于存放指向pthread_t类型的指针(指向该线程tid号的地址)
第二个参数表示了线程的属性,一般以NULL表示默认属性
第三个参数是一个函数指针,就是线程执行的函数。这个函数返回值为 void*,
形参为 void*
第四个参数则表示为向线程处理函数传入的参数,若不传入,可用 NULL 填充(第四个参数设计到void*变量的强制转化,具体使用后续demo中说明)

返回值:成功,返回0;失败,返回一个错误码。可以使用 perror()函数查看错误原因。

1.1 线程的tid号:

每一个线程都会有一个类似进程的pid号,类似我们的“身份证”,叫做tid号。其实就是一个pthread_t 类型的变量。线程号与进程号是表示线程和进程的唯一标识,但是对于线程号而言,其仅仅在其所属的进程上下文中才有意义。

在pthread库中也有相对应的函数来获取该线程的tid号:

  1. #include <pthread.h>
  2. pthread_t pthread_self(void);
  3. 成功:返回线程号

例程:

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. void* pthread1Handler()
  5. {
  6. printf("%ld\n",pthread_self());
  7. }
  8. int main()
  9. {
  10. int back;
  11. pthread_t th1;
  12. back = pthread_create(&th1,NULL,pthread1Handler ,NULL);
  13. if(back != 0){
  14. printf("create pthread failed\n");
  15. perror("Why: \n");
  16. return -1;
  17. }
  18. printf("main:%ld\n;th1:%ld\n ",pthread_self(),th1);
  19. sleep(1);
  20. return 0;
  21. }

结果:
 

 此输出结果因为涉及到线程之间的资源争夺,main函数仅打印了部分数据,后半部分数据被th1线程争夺输出了结果。当主线程伴随进程结束时,所创建出来的线程也会立即结束,不会继续执行。并且创建出来的线程的执行顺序是随机竞争的,并不能保证哪一个线程会先运行。

        2、为线程传入参数

        pthread_create() 的最后一个参数的为 void*类型的数据,表示可以向线程传递一个 void*数据类型的参数,线程的回调函数中可以获取该参数。该参数可以是任意类型的变量,如普通int型变量、也可以是一个指针,同时也可以是结构体。其中涉及到了void* 参数的强制转换,以及一些细节需要注意!!!

        2、1如何传入一个普通变量(以int型变量为例,同时传递一个指针变量加以区分)

例程:

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. void* pthread1Handler(void* arg)
  5. {
  6. printf("get arg = %d,addr: %p\n",(int)(long)arg,arg);
  7. }
  8. void* pthread2Handler(void* arg)
  9. {
  10. printf("get arg = %d,addr: %p\n",*(int*)arg,arg);
  11. }
  12. int main()
  13. {
  14. int ret;
  15. int a = 100;
  16. pthread_t th1;
  17. pthread_t th2;
  18. ret = pthread_create(&th1,NULL,pthread1Handler,(void*)(long)a); //向pthread1传入变量a的值
  19. if(ret != 0){
  20. printf("create pthread failed\n");
  21. perror("Why: \n");
  22. return -1;
  23. }
  24. ret = pthread_create(&th2,NULL,pthread2Handler,(void*)&a); //向pthread2传入变量a的地址
  25. if(ret != 0){
  26. printf("create pthread failed\n");
  27. perror("Why: \n");
  28. return -1;
  29. }
  30. sleep(1);
  31. printf("This is %d ,addr:%p\n",a,&a);
  32. return 0;
  33. }

结果:


输出的结果中,因为pthread2传入的局部变量a的地址,所以其打印的结果与main函数中打印出的地址是相同的。pthread1当中传入的是变量的值,因此在pthread1中打印的地址仅仅是pth1回调函数中行参的地址,在线程结束后,该地址就会被释放。

如何传入?(地址、变量值)

地址:使用取地址符号 &,传入该变量的地址。但是在编译的过程当中,因为在该函数要求传入的参数类型为(void*),与传入的类型不符,需要将传入的数据强制转换成 void*。

使用:在回调函数当中,可以将 void* 强制转换成为 int* 变量,因为该指针当中存放的是main函数当中a变量的地址,访问a变量需要取int*当中的值。(*(int*)arg)

变量值(以 int类型 为例):传递int类型的变量值时,只需要将该类型的变量强制装换成void*传入即可((void*)(long)a)。针对不同位数机器,指针对其字数不同,需要 int 转化为 long
在转指针,否则可能会发生警告

使用sizeof(void *) 和 sizeof(int)的输出它们的大小分别为8和4(不同的操作系统不一样)所以编译后才出现int 到 (void *)转换大小不匹配.

使用:在回调函数当中,可以直接将该变量转换成相应的变量类型使用即可。

错误使用:不要传入动态变化的参数。

例程:

  1. void* func(void* arg)
  2. {
  3. int value = *(int*)arg;
  4. printf("%d\n",value);
  5. }
  6. int main(void)
  7. {
  8. pthread_t pid[6];
  9. int ret;
  10. for (int i=0; i<6; ++i)
  11. {
  12. if ((ret=pthread_create(&pid[i],NULL,thread,(void*)&i)) != 0)
  13. {
  14. fprintf(stderr,"pthread_create:%s\n",strerror(ret));
  15. exit(1);
  16. }
  17. }
  18. }
  1. void* func(void* arg)
  2. {
  3. int value = *(int*)arg;
  4. //free(arg);//------------------->在参数使用结束后,对malloc的内存释放。
  5. printf("%d\n",value);
  6. pthread_exit(arg);
  7. }
  8. int main(void)
  9. {
  10. pthread_t pid[6];
  11. int ret;
  12. void *ptr;
  13. for (int i=0; i<6; ++i)
  14. {
  15. int *p = malloc(sizeof(*p));
  16. if(p==NULL)
  17. {
  18. perror("malloc");
  19. exit(1);
  20. }
  21. *p = i;
  22. if ((ret=pthread_create(&pid[i],NULL,thread,(void*)p)) != 0)
  23. {
  24. fprintf(stderr,"pthread_create:%s\n",strerror(ret));
  25. exit(1);
  26. }
  27. }
  28. for(int j=0;j<6;j++)
  29. {
  30. pthread_join(pid[i],&ptr);//-----------收到线程传过来的参数。
  31. free(ptr);//-----------------释放内存
  32. }
  33. exit(0);
  34. }

在第一个例程中,输出的结果可能会出现错误,传输的是一个动态变化的内存空间,因为线程的创建是需要时间的,但是循环地址空间的内容变化的时间将会快于这个时间,所以会导致内容出错。例程二当中访问的是动态创建的内存空间,里面的内容是固定的,因此每一次传参访问的过程不会出现问题。

        2.2传递一个结构体

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. struct test
  6. {
  7. int Id;
  8. char name[100];
  9. int sex;
  10. };
  11. void* func1(void* arg)
  12. {
  13. struct test* str = (struct test*) arg;
  14. printf("This is %s,Id: %d,sex: %d\n",str->name,str->Id,str->sex);
  15. }
  16. int main()
  17. {
  18. pthread_t pth1;
  19. int ret;
  20. struct test stu1;
  21. stu1.Id = 1;
  22. strcpy(stu1.name,"Zhangzhiyi");
  23. stu1.sex = 1;
  24. ret = pthread_create(&pth1,NULL,func1,(void*)&stu1);
  25. if(ret != 0){
  26. printf("Pthreat_create failed\n");
  27. perror("Why:\n");
  28. return -1;
  29. }
  30. sleep(1);//添加该函数为了能够是子线程先执行,看到输出的结果
  31. printf("main: This is %s,Id: %d,sex: %d\n",stu1.name,stu1.Id,stu1.sex);
  32. return 0;
  33. }

结果:

 总结:以上是线程在创建过程当中会出现的一些问题的归纳与总结,后续如有会继续补充。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/181998
推荐阅读
相关标签
  

闽ICP备14008679号