C语言——多线程基础(pthread)

目录

 

1. 线程的定义以及线程的创建

1.1 线程和进程的概念

1.2 使用pthread_create()函数创建进程

2. 使用pthread_join()等待线程结束

2.1 使用pthread_join()等待线程结束

2.1 使用pthread_join()得到线程函数的返回值


1. 线程的定义以及线程的创建

1.1 线程和进程的概念

线程:进程中的一个实体,是CPU调度和分派的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。线程在运行中呈现间断性。(以上来自《计算机四级教程——操作系统原理》)

谈到线程,就有必要说说进程的定义:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。(以上来自《计算机四级教程——操作系统原理》)

进程的定义有点绕口,我们看看进程由什么组成程序数据进程控制块。其中程序,对应进程定义中“具有一定独立功能的程序”,但是进程除了程序本身,还需要有数据(可以理解为资源),以及,进程控制块。数据和进程控制块是程序运行时必不可少的资源程序依赖这些资源进行相应的活动,就是我们说的“进程”了。

进程的两个基本属性:

  • 进程是一个可拥有资源的独立单位;
  • 进程是一个可以独立调度和分派的基本单位。

线程建立之初,就是为了将进程的上述两个属性分开线程构成了“CPU调度和分派的基本单位”,这样一个进程中可以有很多线程操作系统对线程进行调度和分派,可以更好地实现进程的并打执行同时同一个进程下的线程可以共享该进程的全部资源,可以满足同一个进程下不同线程对进程资源的访问线程的出现,巧妙地将进程的两个属性分开,使得进程可以更好地处理并行执行的需求。

1.2 使用pthread_create()函数创建进程

pthread_create()函数的声明如下:

#include <pthread.h>//需要添加pthread.h头文件
int pthread_create(
                 pthread_t *restrict tidp,   //指向线程标识符的指针,用pthread_t创建
                 const pthread_attr_t *restrict attr,  //设置线程属性,默认为NULL
                 void *(*start_rtn)(void *), //线程运行函数的起始地址
                 void *restrict arg //线程运行函数的起始地址,默认为NULL
                  );

基本的创建一个线程的程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* ptintf_hello_world(void* tid);

int main(void){
    pthread_t thread;
    int status,i=10;
    printf("Main here. Creating thread %d\n",i);
    status=pthread_create(&thread,NULL,ptintf_hello_world,(void*)i);
    
    pthread_join(thread,NULL);  //pthread_join函数以阻塞的方式等待指定的线程结束;如果线程已经结束,函数会立即返回,并且指定的线程必须是joinable的 
    	
    if(status!=0){
        printf("pthread_create returned error code %d\n",status);
    	exit(-1);
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);
	exit(0);
}

2. 使用pthread_join()等待线程结束

2.1 使用pthread_join()等待线程结束

我们先看看下面的程序:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10

void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){//循环创建10个现场
    	printf("Main here. Creating thread %d\n",i);
        //创建线程,线程函数传入参数为i
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
		if(status!=0){//线程创建不成功,打印错误信息
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);//在线程函数中打印函数的参数
	pthread_exit(0);
}

由于我们没有在主线程中等待我们创建出来的10个线程执行完毕,所以创建出来的子线程可能还没来得及执行,就因为主线程(main函数)执行完毕而终止整个进程,导致子线程没法运行。因此printf得到的“Hello world ”不是10个,其数量是无法预知的,其顺序也是无法预知的。如下图:

此时我们就需要pthread_join()函数等待线程执行完成。pthread_join()函数的原型如下:

int pthread_join(pthread_t thread,    //线程标识符,即线程ID,标识唯一线程
                 void **retval);    //用户定义的指针,用来存储被等待线程的返回值。
//返回值:0:成功;其他:失败的错误号

使用pthread_join()函数之后的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10

void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){//循环创建10个现场
    	printf("Main here. Creating thread %d\n",i);
        //创建线程,线程函数传入参数为i
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
		if(status!=0){//线程创建不成功,打印错误信息
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
for(i=0;i<NUMBER_OF_THREADS;i++){
		pthread_join(threads[i],NULL);
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);//在线程函数中打印函数的参数
	pthread_exit(0);
}

上述使用pthread_join()函数的代码的结果如下:

可以看出,此时所有子线程都执行完毕,打印了对应的“Hello world ”,但是线程执行的顺序是不固定的,也就是说,我们无法预知打印的顺序。根据代码判断程序的输出就是不可行的,我们只知道输出的内容,但是不知道输出的顺序。

除非我们在每个子线程创建之后,一直等待其运行结束,然后才开始创建下一个子线程。即,将pthread_join()函数放到紧挨着pthread_create()函数的后面,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10

void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){//循环创建10个现场
    	printf("Main here. Creating thread %d\n",i);
        //创建线程,线程函数传入参数为i
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
        pthread_join(threads[i],NULL);
		if(status!=0){//线程创建不成功,打印错误信息
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}
	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);//在线程函数中打印函数的参数
	pthread_exit(0);
}

此时,我们实际通过pthread_join()函数将多线程的并行强制为顺序执行,此时打印的输出如下图:

可以看出,此时打印的输出是有序的,因为pthread_join()函数将等待对应的线程结束,线程资源被收回,才继续执行下面的命令,创建另一个线程。实际相当于并行变为了串行。

2.1 使用pthread_join()得到线程函数的返回值

除此之外,使用pthread_join()函数还可以利用其第二个参数,得到线程函数的返回值。代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_THREADS 10

void* ptintf_hello_world(void* tid);
int main(void){
    pthread_t threads[NUMBER_OF_THREADS];
    int status,i;
    for(i=0;i<NUMBER_OF_THREADS;i++){
    	printf("Main here. Creating thread %d\n",i);
    	status=pthread_create(&threads[i],NULL,ptintf_hello_world,(void*)i);
		//使用res得到线程函数的返回值 
        int** res=(int**)malloc(sizeof(int*));
		pthread_join(threads[i],(void**)res);  //pthread_join函数以阻塞的方式等待指定的线程结束;如果线程已经结束,函数会立即返回,并且指定的线程必须是joinable的 
    	printf("res[%d]:%d\n",i,**res);//打印线程函数的返回值 
		free(*res); //释放线程处理函数中使用malloc分配的内存空间 
		if(status!=0){
    		printf("pthread_create returned error code %d\n",status);
    		exit(-1);
		}
	}

	exit(0);
}
void* ptintf_hello_world(void* tid){
	printf("Hello world %d.\n",tid);
	int* a=(int*)malloc(sizeof(int));
	*a=(int)tid*(int)tid;
	return a;	//线程函数的返回值 
}

我们新建了一个int**类型的变量res,用来接收线程处理函数的返回值,使用pthread_join()函数得到返回值之后,printf打印出来。与此同时,在线程处理函数中,我们将函数的传入参数tid进行平方运算运算结果作为线程处理函数的返回值

执行输出结果如下:

  • 49
    点赞
  • 214
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值