主要是想记录一下自己的学习过程,对有关概念的解释使用的是偏向于口头的语言,以及自己目前的理解,有点类似于随笔,逻辑性可能较差,理解上难免有一些错误,希望可以给予指正。如果以后自己有遗忘的知识点也可以方便回顾。最近一段时间主要在学Linux系统编程,今天了解了有关Linux下多线程的一些知识,大概总结一下吧。
进程,线程,并发,并行:
1.进程:正在执行的程序,资源分配的最小单位;
2.线程:又称轻量级进程,是进程中的一个实体,程序执行的最小单位;
ps:通俗的理解,进程是运行在系统里的,用的都是系统分配的资源;而线程是运行在进程里面的,用的都是进程分配的资源
3.并发:一个CPU在多个任务间快速切换,给人造成多个任务同时执行的假象;(我感觉就像高速公路上4车道,一排同时有4个车,但是只有一个驾驶员,所以他在某一瞬间,只能驾驶其中的一辆汽车)
4.并行:多个CPU同时执行多个任务,因为是多个CPU,所以多个任务真的是在同时进行;(同样就像高速公路上4车道,一排同时有四个车,四个驾驶员同时在驾驶车辆)
现在电脑都是多核的,所以多线程就可以让多个CPU同时执行一个进程,从而提高效率,在多线程编程实践中,线程的个数往往多于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程。
线程ID号获取、创建、退出、等待
1.获取线程ID
#include<pthread.h> pthreard_t pthread_self(void);
pthread_t其实就是int但是你要用一个变量去接收这个函数的返回值时,他的类型必须是pthread_t 而不能是int。
2.线程创建
#include<pthread.h> int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
返回值: 成功:0;失败 :-1
第一个值为新创建的线程的ID号,因为要回写,所以用了指针,第二值是设置新线程的属性,一般用NULL即可,第三个是一个函数指针,指向线程开始的函数,传一个类型为void *函数名(void *arg)的函数的函数名,第四个就是线程开始函数的参数,如果没有写NULL即可。
3.线程退出
#include<pthread.h> void pthread_exit(void *retval);
分为两种情况:
①主线程中,写在main函数结束的时候,因为如果主线程执行完了,进程就会立即结束,而如果主线程创建的线程还没有执行结束,就会因为主线程的结束而强制结束,为了避免这种情况,所以调用这个函数,主线程会等待所有线程都结束的时候才会终止;
②子线程中(我不知道这种叫法是否准确),会立即结束子线程,并返回void *retval,大多数情况下是没有返回值的,所以如果要返回值,会配合pthread_join()使用;
4.线程等待
#include<pthread.h> int pthread_join(pthread_t tid,void **rval);
返回值: 成功:0;失败 :错误码
正如上面提到的这个函数一般会配合pthread_exit()使用,用来接收pthread_exit()的返回值,这个函数会让线程处于阻塞的状态,第一个参数是其等待的线程ID,第二个参数是用来接收返回值; (其实我不太理解的一点是,pthread_exit()函数的返回值是void *retval类型,也就是说其使用void *类型来接收这个返回值不就可以了吗?为什么要用二级指针呢?我自己猜测应该是因为pthread_join()这个函数规定的吧,也就是本来void *也是可以的)
下面来总结一下我写程序中遇到的一些问题和需要注意的一些地方:
1.虽然一个进程里面的多个线程可以共用全局变量,或者创建新线程的时候可以直接传递指针,从而省去使用pthread_exit()函数进行返回值的操作,但是这样会导致一些安全的问题,所以不建议这样做;
2.还是就是pthread_exit(void *retval),虽然他的返回值是一个指针,但是不能直接int *p,返回p因为线程结束,空间就会被释放,所以必须用malloc,int *p = (int *)malloc(sizeof(int)),来进行分配空间;
3.第三点是pthread_exit(void *retval)可以直接返回int或者char类型的数据(因为其数据的排列方式是整形排列方式),不用使用malloc来分配空间,具体写法:
子线程:int num = 1314; pthread_exit ( (void *) num);
主线程:int ret; pthread_join( id, &ret);
但是double,struct以及其他类型数据就不可以;
4.如果需要在屏幕上打印出线程的ID号建议使用%u,因为线程ID号一般数值较大,已经超出了%d的范围;
5.编译时要链接第三方类库:gcc -lpthread
eg:gcc -lpthread test.c
最后附一段代码,方便理解。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
/*
* 目的:写一个向线程传递结构体,并返回结构体的模版,实现pow()函数
*/
//定义传递参数的结构体
typedef struct info{
double a;
int b;
} ARG;
//定义存储返回值的结构体
typedef struct ret{
double r;
} RET;
//新线程的开始函数
void * love(void *arg);
int main(void)
{
//定义相关变量
int i;
pthread_t pid;
ARG data = {1.31,4};
RET *r;
//创建一个新的线程
pthread_create(&pid, NULL, love, &data);
//方便观察到多线程同时执行的效果
for(i = 1; i <= 10; ++i)
{
printf("process_01: %d\n",i);
sleep(1);
}
//阻塞等待指定线程返回结果
pthread_join(pid,&r);
//将创建线程计算的结果打印到屏幕上
printf("%lf ^ %d = %lf\n",data.a,data.b,r->r);
//调用一下线程退出函数
pthread_exit(NULL);
free(r);
}
/*
* 描述:新线程开始函数
* 功能:计算一个数的指定次方并返回结果
*/
void * love(void *arg)
{
//定义相关变量
ARG *data = (ARG *)arg;
RET *p = (RET *)malloc(sizeof(RET));//线程执行结束空间会被释放,所以使用malloc分配空间
int i;
double num = 1;
//打印相关提示,表名两个线程在同时运行
printf("process_02: start\n");
//计算出结果
for(i = 0; i < data->b; ++i)
{
num *= data->a;
}
sleep(5);
//将要返回的值存储在刚才分配的空间中
p->r = num;
printf("process_02: end\n");
//使用线程退出函数返回计算结果
pthread_exit(p);
}