一、为什么需要使用多线程编程?
当在执行某些程序的时候难免会需要同时执行两个、甚至多个任务,当然可以使用多个进程进行执行,但是难免需要用到信息的传输,因此就需要引入进程间通信的问题,这对于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;
}
结果:
总结:以上是线程在创建过程当中会出现的一些问题的归纳与总结,后续如有会继续补充。