赞
踩
一、为什么需要使用多线程编程?
当在执行某些程序的时候难免会需要同时执行两个、甚至多个任务,当然可以使用多个进程进行执行,但是难免需要用到信息的传输,因此就需要引入进程间通信的问题,这对于CPU内存调度的压力也会更大。多线程编程的优点是在同一个进程下,多个线程可以访问访问同一个全局变量,这使得多个线程之间的沟通交互更加便捷,对CPU资源消耗也会越少。(在Linux系统中,调度是以线程为单位的;但是资源的分配是以进程为单位的)
二、如何使用:
1、如何创建线程?
- #include <pthread.h>
-
- int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
-
- RETURN VALUE
- 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号:
- #include <pthread.h>
- pthread_t pthread_self(void);
- 成功:返回线程号
例程:
- #include <stdio.h>
- #include <pthread.h>
- #include <unistd.h>
-
- void* pthread1Handler()
- {
- printf("%ld\n",pthread_self());
-
- }
-
- int main()
- {
-
- int back;
- pthread_t th1;
-
- back = pthread_create(&th1,NULL,pthread1Handler ,NULL);
-
- if(back != 0){
- printf("create pthread failed\n");
- perror("Why: \n");
- return -1;
- }
-
- printf("main:%ld\n;th1:%ld\n ",pthread_self(),th1);
-
- sleep(1);
-
- return 0;
- }
结果:
此输出结果因为涉及到线程之间的资源争夺,main函数仅打印了部分数据,后半部分数据被th1线程争夺输出了结果。当主线程伴随进程结束时,所创建出来的线程也会立即结束,不会继续执行。并且创建出来的线程的执行顺序是随机竞争的,并不能保证哪一个线程会先运行。
2、为线程传入参数
pthread_create() 的最后一个参数的为 void*类型的数据,表示可以向线程传递一个 void*数据类型的参数,线程的回调函数中可以获取该参数。该参数可以是任意类型的变量,如普通int型变量、也可以是一个指针,同时也可以是结构体。其中涉及到了void* 参数的强制转换,以及一些细节需要注意!!!
2、1如何传入一个普通变量(以int型变量为例,同时传递一个指针变量加以区分)
例程:
- #include <stdio.h>
- #include <pthread.h>
- #include <unistd.h>
-
- void* pthread1Handler(void* arg)
- {
- printf("get arg = %d,addr: %p\n",(int)(long)arg,arg);
- }
-
- void* pthread2Handler(void* arg)
- {
- printf("get arg = %d,addr: %p\n",*(int*)arg,arg);
- }
-
- int main()
- {
-
- int ret;
- int a = 100;
-
- pthread_t th1;
- pthread_t th2;
-
- ret = pthread_create(&th1,NULL,pthread1Handler,(void*)(long)a); //向pthread1传入变量a的值
-
- if(ret != 0){
- printf("create pthread failed\n");
- perror("Why: \n");
- return -1;
- }
-
- ret = pthread_create(&th2,NULL,pthread2Handler,(void*)&a); //向pthread2传入变量a的地址
- if(ret != 0){
- printf("create pthread failed\n");
- perror("Why: \n");
- return -1;
- }
-
- sleep(1);
-
- printf("This is %d ,addr:%p\n",a,&a);
-
- return 0;
- }
结果:
输出的结果中,因为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 *)转换大小不匹配.
使用:在回调函数当中,可以直接将该变量转换成相应的变量类型使用即可。
错误使用:不要传入动态变化的参数。
例程:
- void* func(void* arg)
- {
- int value = *(int*)arg;
- printf("%d\n",value);
- }
-
- int main(void)
- {
- pthread_t pid[6];
- int ret;
- for (int i=0; i<6; ++i)
- {
- if ((ret=pthread_create(&pid[i],NULL,thread,(void*)&i)) != 0)
- {
- fprintf(stderr,"pthread_create:%s\n",strerror(ret));
- exit(1);
- }
- }
- }
- void* func(void* arg)
- {
- int value = *(int*)arg;
- //free(arg);//------------------->在参数使用结束后,对malloc的内存释放。
- printf("%d\n",value);
- pthread_exit(arg);
- }
-
- int main(void)
- {
- pthread_t pid[6];
- int ret;
- void *ptr;
- for (int i=0; i<6; ++i)
- {
- int *p = malloc(sizeof(*p));
- if(p==NULL)
- {
- perror("malloc");
- exit(1);
- }
-
- *p = i;
- if ((ret=pthread_create(&pid[i],NULL,thread,(void*)p)) != 0)
- {
- fprintf(stderr,"pthread_create:%s\n",strerror(ret));
- exit(1);
- }
- }
-
-
- for(int j=0;j<6;j++)
- {
- pthread_join(pid[i],&ptr);//-----------收到线程传过来的参数。
- free(ptr);//-----------------释放内存
-
- }
- exit(0);
- }
在第一个例程中,输出的结果可能会出现错误,传输的是一个动态变化的内存空间,因为线程的创建是需要时间的,但是循环地址空间的内容变化的时间将会快于这个时间,所以会导致内容出错。例程二当中访问的是动态创建的内存空间,里面的内容是固定的,因此每一次传参访问的过程不会出现问题。
2.2传递一个结构体
- #include <stdio.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
-
- struct test
- {
- int Id;
- char name[100];
- int sex;
- };
-
-
- void* func1(void* arg)
- {
- struct test* str = (struct test*) arg;
-
- printf("This is %s,Id: %d,sex: %d\n",str->name,str->Id,str->sex);
- }
-
- int main()
- {
- pthread_t pth1;
- int ret;
-
- struct test stu1;
-
- stu1.Id = 1;
- strcpy(stu1.name,"Zhangzhiyi");
- stu1.sex = 1;
-
- ret = pthread_create(&pth1,NULL,func1,(void*)&stu1);
-
- if(ret != 0){
- printf("Pthreat_create failed\n");
- perror("Why:\n");
- return -1;
- }
-
- sleep(1);//添加该函数为了能够是子线程先执行,看到输出的结果
-
- printf("main: This is %s,Id: %d,sex: %d\n",stu1.name,stu1.Id,stu1.sex);
-
- return 0;
- }
结果:
总结:以上是线程在创建过程当中会出现的一些问题的归纳与总结,后续如有会继续补充。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。