---其实经过这一段时间的Linux应用编程学习,自己总结发现到,在Linux应用编程当中有四大模块我们一定要掌握(这些是最基础的东西):
而且再结合招聘平台上的要求,可以看到,要求里面大都会或多或少设计到上面中的一点。现在我们先从多线程开始学习,后面再慢慢的讲解其它模块的编程。
其实以前在没学进程和线程之前,自己对这个充满了疑惑,上网一查,全是讲他们的区别,看了一点区别就懵圈了,因为从一开始就没弄懂进程和线程他们本质概念。现在回过头来想想也是当时,如果连这个东西叫什么东西都不知道,你就去用它其它特性,那简单是在浪费时间,做无用功。
一、线程介绍:
1、什么是线程?
说到这个线程,那么我们先从什么是进程开始说起(这里也可以看我之前写的文章——Linux系统下进程编程(一))。在这里我想用形象的比喻给大家介绍这两个东西(当然比喻的可能不是很好,不过目的是为了理解,hh):
想必每个人电脑上都安装了许多应用程序app吧,当你打开你的电脑时,按照你事先想好的方案(我打开电脑要做什么呢,肯定是要用到电脑上安装的app啦),我们点击桌面上的app,就会运行它了,这个时候,你可以按快捷——ctrl+shift+esc,打开Windows的任务管理器,你将会看到如下面的图片所示:
从这个图片当中我们可以看到TIM这个是我们常用的app(也就是进程),所以从中我们可以用一句简单的话来说:进程就是我们运行的程序(app)。
简单的说完了什么是进程,那么线程是啥?不知道读者有没有仔细观察,在进程下面是不是还显示了一些其他东西要运行呢,没错这个就是我们要说的线程。线程简单来说,就是在一个app(运行程序)里面干着一些细小的细活呢,没有线程的任劳任怨,进程就得不到完美解释了!
2、术语来介绍线程和进程的联系:
进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。
线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程可以由多个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
注意: 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径,所以进程挂掉了,线程就完蛋了,就比如上面的那个TIIM,你把它给关了,你就没得玩了(我看网上好多博客里面都说,一个线程死掉了,进程就死掉了,这里和我理解的有点出路,如果这里我理解错误的话,欢迎读者来讨论或者批评指正,虚心学习)。线程有自己的堆栈和局部变量,但线程没有单独的地址空间。
3、为什么还要用线程来编写程序,不是有已经进程编程了吗?
a、使用多线程的理由之一:是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
b、使用多线程的理由之二:是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
经典语句:
二、线程函数介绍:
1、线程的创建函数 (主线程用来创造子线程的):
在ubuntu中我们使用man 3 pthread_create 来查看它的用法:
PTHREAD_CREATE(3)
Linux Programmer's Manual
PTHREAD_CREATE(3)
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
分析参数说明:
第一个参数pthread_t *thread :thread是一个指针,指向pthread_t 类型的数据 ,&thread为前面定义的一个pthread_t 类型对象的地址,以获取新建线程的标识符。
第二参数const pthread_attr_t *attr:指定创建线程的属性,如线程优先级、初始栈大小、是否为守护进程等。可以使用NULL来作为默认值,通常情况下我们都是使用默认值。
第三个参数void *(*start_routine) (void *):它是一个函数指针,可以联系到数组指针来看它,可以把它看成 void *(*)(void *) start_routine ,所以这个参数的作用是指定当新的线程创建之后,将要执行的函数。
第四个参数void *arg:线程将执行的函数的参数。如果想传递多个参数,请将它们封装在一个结构体中。
创建成功,返回0,失败返回error number:
RETURN VALUE
On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
2、线程的合并和回收函数:
在介绍这两个函数之前,我们要明白什么是线程的合并和回收。我们在第一个函数pthread_create()负责创建了一个线程。那么线程也属于系统的资源,这跟内存没什么两样,而且线程本身也要占据一定的内存空间(只不过它是共享进程的内存)。众所周知的一个问题就是C或C++编程中如果要通过malloc()或new分配了一块内存,就必须使用free()或delete来回收这块内存,否则就会产生著名的内存泄漏问题。既然线程和内存没什么两样,那么有创建就必须得有回收,否则就会产生另外一个著名的资源泄漏问题,这同样也是一个严重的问题。那么线程的合并就是回收线程资源了(和我们之前讲解的回收僵尸进程是一个道理,不能浪费内存资源)。
线程的合并是一种主动回收线程资源的方案。当一个线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用线程,直到被合并的线程结束为止。当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()。线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。
线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。
下面还是老办法还是man手册来查看他们的函数原型:
PTHREAD_JOIN(3)
Linux Programmer's Manual
PTHREAD_JOIN(3)
NAME
pthread_join - join with a terminated thread
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
分析和说明:
第一个参数pthread_t thread:指定要等待的线程thread。
第二个参数 void **retval :如果不为NULL,那么线程的返回值存储在status指向的空间中(这就是为什么status是二级指针的原因!和之前介绍的输出型参数类似——c专题之指针----指针与函数传参和输入、输出型参数) 。
返回值:
RETURN VALUE
On success, pthread_join() returns 0; on error, it returns an error number.
pthread_detach()函数原型:
PTHREAD_DETACH(3)
NAME
pthread_detach - detach a thread
SYNOPSIS
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
分析说明:
这里的参数就是传入我们之前创建成功了的线程。
3、线程取消函数:
a、pthread_cancel(),一般都是主线程调用该函数去取消(让它赶紧死)子线程:
PTHREAD_CANCEL(3)
Linux Programmer's Manual
PTHREAD_CANCEL(3)
NAME
pthread_cancel - send a cancellation request to a thread
SYNOPSIS
#include <pthread.h>
int pthread_cancel(pthread_t thread);
Compile and link with -pthread.
分析说明:
这个函数唯一的参数就是我们要终止的线程的标识符thread。
b、pthread_exit()函数:
PTHREAD_EXIT(3)
Linux Programmer's Manual
PTHREAD_EXIT(3)
NAME
pthread_exit - terminate calling thread
SYNOPSIS
#include <pthread.h>
void pthread_exit(void *retval);
Compile and link with -pthread.
分析说明:
这个函数的作用是,终止调用它的线程。函数参数是函数返回时的代码。注意一点的是这函数必须在pthread_join()函数第二个参数不为NULL的前提下才能够执行 ----------------这个pthread_exit()函数写在被调用子线程中
4、获取线程ID函数(这个函数使用比较简单):
NAME
pthread_self - obtain ID of the calling thread
SYNOPSIS
#include <pthread.h>
pthread_t pthread_self(void);
Compile and link with -pthread.
DESCRIPTION
The pthread_self() function returns the ID of the calling thread. This is the same value that is returned in *thread in the pthread_create(3) call that created this thread.
RETURN VALUE
This function always succeeds, returning the calling thread's ID.
三、总结:
今天主要是简单的介绍了一下线程的概念和一些基本函数使用,当然还有一些函数没介绍完(比如属性函数,这里就先不介绍了,在后面的实战当中如有要使用,我们再来介绍也不迟,先把今天介绍的几个函数吸收掉,好方便后面的学习)。好的,今天的分享就到这里,明天继续分享线程的系列文章实战篇。