Linux的多线程--初识

来源:互联网 发布:杰视帮美工教程 编辑:程序博客网 时间:2024/05/18 21:49

什么是线程?

说到线程,我们并不是很了解,之前学过一点进程的知识,那么线程与进程有什么关系呢?

我们知道,进程在各个独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制,但是呢我们这里在Linux下学习的线程则是在一个进程的地址空间中执行的多个线程。在Linux的环境下并没有真正意义下的线程,线程是通过进程来模拟的。由于同一进程的多个线程共享同一地址空间,因此Text SegmentData Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一 个全局变量,在各线程中都可以访问到。

虽然Linux下没有真正的线程,但是Windows系统下还是存在线程的,所以在Linux下模拟实现的我们又称作为轻量级进程(LWP)

我们大部分人都会下载电影对吧,那么很多人应该使用过迅雷之类的下载软件对吧,其实这些下载软件在下载电影时就用到了线程的概念。你可以同时下载多个电影,打开迅雷这个进程之后,你可以打开下载线程进行下载电影,同时你也可以打开播放线程进行播放,由这我们不难理解,进程是资源分的基本单位,而线程则是调度的基本单位

当然,我们使用这类软件的事后也知道,你不可能在关闭软件后还能进行电影的播放或是下载对吧,所以我们可以再次学到在多线程运行时,一定要让主线程等待新线程结束后再结束释放资源

线程属性

上面我提到在Linux下没有真正意义上的线程,Linux下的线程是通过进程去模拟实现的,所以又叫做轻量级进程。我们知道每一个进程都由操作系统通过PCB块来控制,进程通过PCB与地址空间与页表的合作访问物理空间。


当我们创建多个线程的时候,我们就会通过多个PCB(包括主线程)共享一块地址空间,即同享一份资源和环境。


各线程主要共享以下进程资源和环境:

1. 文件描述符表

2. 每种信号的处理方式(SIG_IGNSIG_DFL或者自定义的信号处理函数)

3. 当前工作目录

4. 用id和组id

但有些资源是每个线程各有一份:

1. 线程id

2. 上下文,包括各种寄存器的值、程序计数器和栈指针

3. 栈空间

4. errno变量

5. 信号屏蔽字

6. 调度优先级 

线程操作函数

创建线程

上面我提到线程各有一份的包括线程id,其实这就跟进程的id是一个意思,只不过进程id在整个系统中都是唯一的,而线程id只有在所属进程的上下文中才有意义。线程id由数据类型pthread_t 提供。

我们通过pthread_create()函数来创建线程。



参数:

  • thread,为一个pthread_t类型的指针,指向一个内存单元。新创建的线程的线程ID会被设置在thread指向的内存单元。
  • attr,用于定制各种不同的线程属性。
  • start routine,为一个函数指针,新创建的线程从start routine函数的地址开始运行。该函数的参数是一个无类型指针参数。这个参数后面我们会提到。
  • arg,为一个无类型指针,当我们需要为第三个参数函数指针传参时,那么便需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。可以理解为此arg参数就是上面函数指针的参数。
返回值:
成功时返回0,失败时返回错误码。以前学的系统函数都是成功返回0,失败返回-1,而错误码保存在全局变量error中,pthread库的函数都是通过返回值返回错误码,虽然每个线程也都有一个error,但这是为了兼容其他函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。

示例:


然后运行代码我们可以看到:



上面的代码我们似乎用到了pthread_self()这个函数,它是干嘛的呢?这个函数就是用来获取当前线程的id的,参数是无参

线程终止

如果需要终止某个线程而不终止整个进程,一共有三种方法。

1.从线程函数return。但是这种方法对主线程不适用,从main函数return相当于调用exit;

2.一个线程可以调用pthread_cancel终止统一进程中的另一线程。


3.线程可以调用pthread_exit终止自己。


下面我们通过示例演示一下

我们运行上面的代码可以看到

总结可以得到:
1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值
2. 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELEDLinuxpthread库中PTHREAD_CANCELED的值是-1
3. 如果thread线程自己调用pthread_exit终止,value_ptr所指向的单元存放的是传给pthread_exit的参数。 如果对thread线程的终止状态不感兴趣,可以传NULLvalue_ptr参数。

对了,终止线程还有一种终极方式,那就是直接终止进程,因为线程都是在进程中运行的,调用exit或_exit会终止进程,这样也同时终止了所有线程。

线程等待

在上面的例子中我们还看到了pthread_join()这个函数,其实这个函数的功能就是线程等待,即等待一个线程的结束。

第一个参数为线程id,第二个用来接收新线程的退出码。而返回值则是成功返回0,失败返回错误号。


线程的分离

在任何一个时间点上, 线程是可结合的(joinable)或者是分离的(detached。 一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。 相反, 一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终止时由系统自动释放。
线程在默认情况下会被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。
由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在在线程中加入代码pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。
下面用代码作为例子:
运行代码后我们可以看到
即设置为分离的之后pthread_join()函数不能将这个进程处理掉了。