前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >UNPv1第二十三章:线程

UNPv1第二十三章:线程

作者头像
提莫队长
发布2019-02-21 10:53:53
4390
发布2019-02-21 10:53:53
举报
文章被收录于专栏:刘晓杰刘晓杰

在传统的UNIX模型中,当一个进程需要由另一个实体执行某件事时,该进程派生(fork)一个子进程,让子进程去进行处理。UNIX下的大多数网络服务器程序都是这么编写的,这在我们的并发服务程序例子中可以看出:父进程接收连接,派生子进程,子进程处理与客户的交互。

虽然这种模式很多年来使用的很好,但是fork有一些问题:

  1. fork是昂贵的。内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等。目前的实现使用一种称做写时拷贝(copy-on-write)技术,可避免父进程数据空间向子进程的拷贝,除非子进程需要自己的拷贝。尽管有这种优化技术,fork仍然是昂贵的。
  2. fork子进程后,需要用进程间通信(IPC)在父子进程之间传递信息。fork之前的信息容易传递,因为子进程一开始就有父进程数据空间及所有描述字的拷贝。但是从子进程返回信息给父进程需要做更多的工作。

线程有助于解决这两个问题。线程有时候称为轻权进程(lightweight process),因为线程比进程”轻权”。也就是说,创建线程要比创建进程块10~100倍。

一个进程中的所有线程共享相同的全局内存,这使得线程很容易共享信息,但是这种简易性也带来了同步(synchronization)问题。一个进程中的所有线程不仅共享全局变量,而且共享:

代码语言:javascript
复制
1. 进程指令
2. 大多数数据
3. 打开的文件(如描述字)
4. 信号处理程序和信号处置
5. 当前工作目录
6. 用户ID和组ID

但是每个线程有自己的:

代码语言:javascript
复制
1. 线程ID
2. 寄存器集合,包括程序计数器和栈指针
3. 栈(用于存放局部变量和返回地址)
4. errno
5. 信号掩码
6. 优先级

1. 基本线程函数:创建和终止

讲述5个基本线程函数,利用他们代替fork重新编写我们的TCP客户-服务器程序

(1)当一个程序由exec启动时,会创建一个称为初始线程(initial thread)或主线程(main thread)的单个线程。额外线程则由pthread_create函数创建。

代码语言:javascript
复制
#include <pthread.h> 
int pthread_create(pthread_t * tid, const pthread_attr_t * attr, void *  (*func)(void *), void * arg);  
//return: success 0, failed Exxx

一个进程中的每个线程都由一个线程ID(thread ID)标识,其数据类型是pthread_t(常常是unsigned int)。如果新的线程创建成功,其ID将通过tid指针返回。 每个线程都有很多属性(attribute):优先级,起始栈大小,是否应该是一个守护线程,得等。我们通常使用缺省值,将attr参数说明为空指针。 最后,当创建一个线程时,我们要说明一个它将执行的函数。函数的地址由func参数指定,该函数的调用参数是一个指针arg。如果我们需要多个调用参数,我们必须将它们打包成一个结构,然后将其地址当作唯一的参数传递给起始函数。 注意func和arg的声明,func函数取一个通用指针(void )参数,并返回一个通用指针(void )。这就使得我们可以传递一个指针(指向任何我们想要指向的东西)给线程,由线程返回一个指针(同样地,指向任何我们想要指向的东西)。

(2)我们可以调用pthread_join等待一个线程终止。把线程和UNIX进程相比,pthread_create类似于fork,pthread_join类似与waitpid。

代码语言:javascript
复制
#include <pthread.h>
int pthread_join(pthread_t tid, void * * status); 
//返回:成功为0,出错为正的Exxx值

我们必须指定要等待线程的tid。很可惜,我们没有办法等待任意一个线程结束(类似于waitpid的进程ID参数为-1的情况)。我们在讨论图23.14时还将涉及这个问题。如果,status指针非空,线程的返回值(一个指向某个对象的指针)将存放在status指向的位置。

(3)每个线程都有一个ID以在给定的进程内标识自己。线程ID由pthread_create返回,我们也看到了它在pthread_join中的使用。线程用pthread_self取得自己的线程ID。

代码语言:javascript
复制
#include <pthread.h>
pthread_t pthread_self(void);  //返回:调用线程的线程ID

与UNIX进程相比,线程的pthread_self类似于getpid。

(4)线程或者是可汇合的(joinable)或者是脱离的(detached)。当可汇合的线程终止时,其线程ID和退出状态将保留,直到另外一个线程调用pthread_join。脱离的线程则像守护进程:当它终止时,所有的资源都将释放,我们不能等待它终止。如果一个线程需要知道另一个线程什么时候终止,最好保留第二个线程的可汇合性。 pthread_detach函数将指定的线程变为脱离的。

代码语言:javascript
复制
#include <pthread.h>
int pthread_detach(pthread_t tid); 
//返回:成功为0,出错为正Exxx值

该函数通常被想脱离自己的线程调用,如: pthread_detach( pthread_self() );

(5)终止线程的一种方法是调用pthread_exit

代码语言:javascript
复制
#include <pthread.h>
void pthread_exit(void * status);

如果线程未脱离,其线程ID和退出状态将一直保留到调用进程中的某个其他线程调用pthread_join。 指针status不能指向局部于调用线程的对象,因为线程终止时这些对象也消失。 有两种其他方法可使线程终止: 1. 启动线程的函数(pthread_create的第3个参数)返回。既然该函数必须说明为返回一个void指针,该返回值便是线程的终止状态。 2. 如果进程的main函数返回或者任何线程调用了exit,进程将终止,线程将随之终止。

2. 互斥锁

我们称线程编程为并发编程(concurrent programming)或并行编程(parallel programming),因为多个线程可并发运行并访问相同的变量。虽然我们刚刚讨论的错误情形以单CPU系统为前提,但是如果线程A和线程B在多处理器系统的不同CPU上同时运行,潜在的错误同样存在。在通常的Unix编程中,我们没有遇到这种并发编程问题,因为用fork时,除了描述字外,父进程和子进程不共享任何东西。但是,当我们讨论进程间的共享内存时仍将遇到这类问题。 我们刚刚讨论的问题,即多个线程修改一个共享变量,是最简单的问题。解决方法是用一个互斥锁(mutex, 代表mutual exclusion)保护共享变量。只有我们持有该互斥锁才能访问该变量。在Pthreads中,互斥锁是类型为pthread_mutex_t的变量。我们用下面两个函数为互斥锁加锁和解锁。

代码语言:javascript
复制
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t * mptr);  
        //return: success 0, failed Exxx
int pthread_mutex_unlock(pthread_mutex_t * mptr);  
        //return: success 0, failed Exxx

如果我们试图为一个已被其他线程锁住的互斥锁加锁,程序便会阻塞直到该互斥锁被解锁。 如果互斥锁变量是静态分配的,我们必须将它初始化为常值PTHREAD_MUTEX_INITIALIZER

3. 条件变量

我们需要一中方法使得主循环进入睡眠,直到有一线程通知它某件事已就绪。条件变量(condition variable)加上互斥锁可以提供这种功能。互斥锁提供互斥机制,条件变量提供信号机制。 在Pthreads中,条件变量是一个pthread_cond_t类型的变量。条件变量使用下述两个函数:

代码语言:javascript
复制
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t * cptr, pthread_mutex_t * mptr);
int pthread_cond_signal(pthread_cond_t * cptr);
//return: success 0, failed Exxx

第二个函数的名字中“signal”一词不是指Unix的SIGxxx信号。 为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

设有两个共享的变量 x 和 y,通过互斥量 mut 保护,当 x > y 时,条件变量 cond 被触发。

代码语言:javascript
复制
        int x, y;
        pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

        //等待直到 x > y 的执行流程:
        pthread_mutex_lock(&mut);
        while (x <= y) {
                pthread_cond_wait(&cond, &mut);
        }
        /* 对 x、y 进行操作 */
        pthread_mutex_unlock(&mut);
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年04月26日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 基本线程函数:创建和终止
  • 2. 互斥锁
  • 3. 条件变量
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档