apue学习第十七天(1)——线程基础(第十一章)

这名义上是第十七天,但线程(第11章)和线程控制(第12章)这两章我着实看了一周,当然,收获也不小。说实话,线程对学过操作系统的菜鸟们来说再熟悉不过,进程与线程的概念区别可以说得头头是道,但是我相信大多数人和我一样,对实现细节只能感叹一声:呵呵。


好啦,话不多说,先看线程基础。

我们深入的向process内部探究,就可以看到thread,thread是系统调度的最小单位。一个进程里面有若干线程,比如,我们在用chrome浏览器,一边写博客,一边下载几个文件;这个时候,便可以用一个线程控制用户交互(键盘打字、鼠标点击),另外几个线程负责和网络主机通信(下载)。这些线程共享地址空间,也就是说,.text, .data段都是共享的,也就是说,你定义一个全局变量,当然所有线程都能访问到啦。共享需要处理同步机制,但也提供了很大的方便,不然你让多个进程共享内存、共享file descriptor试试?看看麻烦不?

既然提到了共享,那我们看看线程到底共享了进程中的哪些资源:

  • File descriptor table(想想前面讲文件描述符的地方,每个进程中都有个fd table,维护着需要处理的文件)
  • 每种信号的处理方式(SIG_IGN,SIG_DFL或自定义的信号处理函数)
  • 当前工作目录
  • 用户id和用户组

其实,这些共享的资源都很显然,不用去特意记,下面每个线程独有的东西也很显然,来看吧:

  • 线程id(没id就糟了)
  • Context(下面重点说这个)
  • Stack(这个理所当然,但需要格外注意)
  • errno变量(每个线程都会出错,当然不可能共用一个errno)
  • 信号屏蔽字(想想要单独处理任务就可以了)
  • 调度优先级(做为独立单位调度的嘛)

首先对context进行解释一下,上下文,就是所谓的运行时候register、counter、stack frame pointer的值啊这些环境。操作系统里interrupt的概念还记得吧?中断的时候要保存上下文,然后才能跳到interrupt handler执行,最后返回的时候还要恢复上下文。当然,还有一些比如process context和interrupt context的区别啊,用户态和内核态啊,内核态如何在process context和interrupt context下执行啊这些问题,具体的看这里吧:“用户空间与内核空间,进程上下文与中断上下文[总结]”http://www.cnblogs.com/Anker/p/3269106.html。

好啦,那我们来分析一下这些区别,看的时候,千万不能以上帝视角看,这样只能是死记硬背;要以系统底层的角度来看,系统可不管你是进程还是线程,给它东西它去调用执行就足够了!所以,有些segmentation,file descriptor这些东西当然要共享;id,context,stack,errno等等这些东西当然不能共享,分析一下很容易就记住了。

有一个面试的场景,我们来看一下:

考官:“进程和线程有什么区别?”

面试者:“blablabla...”(也就是从各自概念啊、之间联系啊、区别啊说一通);

考官继续:“一个程序分为哪些段?”

面试者:“.text, .data, .bss, stack, heap等等 

考官追问:“进程和线程都共享这些吗?”

面试者:“当然不是啦!除了stack,其它的进程和线程共享;one thread has a private stack。(它要执行函数调用当然要有自己的stack)”

……

那么,多线程和多核处理器是什么关系?

没有关系!想一想,多线程是程序中编写用来提高并发性的(并发和并行要知道)。处理器的数量并不影响程序的结构,不管几核,程序都可以用线程得到简化;即使对于单处理器,多线程程序在串行化任务中不得不阻塞,由于某些线程在阻塞时还有其它线程可以运行,所以照样改善响应时间和吞吐量。所以,看到了吧,两者一个是硬件处理上的东西,一个是程序结构上的东西,毛关系没有!



我们对线程基础就说那么多了,那么多线程的编程呢?我们只看下表,不追细节(注意是pthread.h中的POSIX线程接口):

apue学习第十七天(1)——线程基础(第十一章)_第1张图片

这是进程与线程控制原语(primitive)的一些联系,很容易理解;可能唯一不好理解的是pthread_join这个join,等待一个进程的终止,我来解释一下。假如A,B两个线程,我要等待他们都结束再执行后面的内容。所以我站在一个点说:"A, join with me!", "B, join with me",等到他们都执行结束到我这里了,我就可以继续向下执行了。

这个关于join的字面解释难登大雅之堂,望原宥~


最后说一下线程属性,这出现在APUE书中第十二章12.3。

那先来看一句话,如果这句话懂了,我想后面也不用看了:The attribute object is opaque to applications.

如果不懂的话,肯定有下面两个问题:

(1)什么是属性对象(attribute object)?

那什么是属性呢?属性是用来规定行为的,pthread接口允许我们通过设置每个对象关联的属性来调整线程和同步对象的行为。我们看,线程和线程属性关联、互斥量和互斥量属性关联,那么每个“对象”都有“自己类型的属性对象”与其关联。一个属性对象可以代表多个属性,类似于线程属性是一个属性对象,包含很多属性。

(2)什么叫不透明(opaque)?

在网络中,transparency指数据从一端传到另一端不会改变,“透明传输”也是类似的;那么显然,在计算机中的“透明性现象”也类似,不用管中间有什么,从这一端到那一端好像没有一样。(http://blog.csdn.net/rnzuozuo/article/details/40652085)

那么,说到opaque data type(不透明数据类型),好像和上面说的有一点偏差(存疑),但这并不妨碍我们理解。

stack overflow上有一些很精彩的解释【http://stackoverflow.com/questions/2301454/what-defines-an-opaque-type-in-c-and-when-are-they-necessary-and-or-useful】,那么我来把它理清楚:

“不透明opaque”,顾名思义,给你个盒子,你看不到里面,也就是这个盒子的内部细节被隐藏了。wikipedia上这样解释opaque data type:

a data type that is incompletely defined in an interface, so that its values can only be manipulated by calling subroutines that have access to the missing information. The concrete representation of the type is hidden from its users.

好吧,说的是“不完全定义的数据类型,其数值只能通过接口操作,实现细节对用户隐藏”。看到这里肯定很懵,因为单单了解表象是很难深入理解它的。好,那我们进一步看【http://stackoverflow.com/questions/735131/what-does-the-term-opaque-type-mean-in-the-context-of-cfbundleref-opaque-type】——他说:An "opaque type" is a type where you don't have a full definition for the struct or class. 但是,什么又叫“full definition”呢?我们直接看代码就很清楚了http://stackoverflow.com/questions/3854113/what-is-an-opaque-value

// header file —— prog.h
struct xyzzy;

struct xyzzy *xyzzyOpen (void);
void xyzzyClose (struct xyzzy *fh);

______________________________________________________

// source file —— prog.c
#include <stdio.h>
#include <stdlib.h>
#include "prog.h"

struct xyzzy { int payload; };
static int payloadVal = 42;

struct xyzzy *xyzzyOpen (void) {
    struct xyzzy *plugh = malloc (sizeof (struct xyzzy));
    plugh->payload = payloadVal++;
    printf ("xyzzyOpen payload = %d\n", plugh->payload);
    return plugh;
}

void xyzzyClose (struct xyzzy *plugh) {
    printf ("xyzzyClose payload = %d\n", plugh->payload);
    free (plugh);
}

______________________________________________________

// main file
#include "prog.h"

int main (void) {
    //struct xyzzy myvar;             // error !!!
    struct xyzzy *num1 = xyzzyOpen();
    struct xyzzy *num2 = xyzzyOpen();
    struct xyzzy *num3 = xyzzyOpen();
    xyzzyClose (num1);
    xyzzyClose (num3);                // these two intentionally
    xyzzyClose (num2);                //   reversed.
    return 0;
}
prog.h是头文件,提供了对结构体xyzzy的使用接口,prog.c是其实现。注意头文件中的struct xyzzy; 这是一个声明,而具体定义则在prog.c中。好啦,我们看main file,#include “prog.h”,会把struct xyzzy的声明引入,但是,编译器compiler(注意是编译器)并不知道struct的实现细节,只知道有这么一个struct。

这就是为什么struct xyzzy myvar 会error的原因(无法得知struct空间大小):

prog1.c: In function ‘main’:
prog1.c:3:15: error: storage size of 'myvar' isn't known

当然,你定义struct xyzzy的指针当然没问题。所以,这就很好的隐藏了struct的内部实现,而只是提供对其操作的接口,所以实现了不透明性。想想FILE *的使用,我们平时只是使用FILE *,你见过FILE f这样的吗?所以FILE也是个典型的opaque type。


我们对opaque说了一大通,正题“线程属性”倒没说多少。书中提到了detached属性(分离状态的线程)、stackaddr属性(栈的最低内存地址)、guardsize属性(控制栈溢出时扩展内存的大小)。可能detached thread要留意一下,其他的都不说啦。

那么,线程基础就讲到这里,下一节,线程同步~


你可能感兴趣的:(apue学习第十七天(1)——线程基础(第十一章))