Bootstrap

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

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

        当在执行某些程序的时候难免会需要同时执行两个、甚至多个任务,当然可以使用多个进程进行执行,但是难免需要用到信息的传输,因此就需要引入进程间通信的问题,这对于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;
}

结果:

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

;