梦见锡安
msgbartop
小舟从此逝,江海寄余生。
msgbarbottom

protothreads | 2009年01月19日

Linux下主要的线程库实现[1]包括LinuxThreads和NPTL。而protothreads综合了事件驱动机制高效和线程机制清晰的优点,是一个更轻量级(extremely lightweight)的线程实现。据介绍,它创建一个线程只需要使用两个字节,此外不再占用其它堆栈空间[2]。需要用gcc编译,但操作系统平台无关。使用BSD风格许可证发布。其它的主要特点[3]还包括:

  • 不提供POSIX或者其它标准API;
  • 代码没有使用汇编或者setjmp(3)之类的技术;
  • CPU架构无关;
  • 明确的、非抢占式的线程调度方式;
  • 只在单CPU上执行;

最新的版本[4]从头重写了全部代码,包括以下文件:

   64 gdbinit
   22 license.txt
   15 Makefile
  218 protothread.c
  178 protothread.h
  109 protothread_lock.c
   62 protothread_lock.h
   36 protothread_sem.c
   21 protothread_sem.h
  711 protothread_test.c
 1436 总用量

全部实现代码只有几百行,另包括一个测试样例。这对嵌入式开发来说应该是个绝好的线程库。决定回家前研究一下~


[1] 见pthreads(7),另参考http://www-128.ibm.com/developerworks/cn/linux/l-threading.html

[2] http://www.sics.se/~adam/pt/

[3] http://protothread.wiki.sourceforge.net/

[4] http://sourceforge.net/projects/protothread/


第十二章 线程控制 | 2008年11月21日

POSIX线程机制定义了多种数据类型,这些数据类型对应用程序来说其内部结构是不透明的。就是说直接访问它们的数据对象是无意义的,而应该使用pthreads(7)库定义的方法去进行访问。各种数据类型对象方法的动作包括初始化、销毁、读取、更改等。

1、线程属性对象

A.初始化和去初始化

在使用线程属性对象之前,应该使用对其进行初始化,如果已经使用结束,应销毁此对象以释放进程空间:

1
2
3
4
#include <pthread.h>
 
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

B.分离属性

下面的函数用于读取和设置线程的分离属性:

1
2
3
4
#include <pthread.h>
 
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

线程的分离属性只有两个选项。为PTHREAD_CREATED_JOINABLE时,线程退出时需要另外一个线程使用pthread_join(3)来回收线程资源。为PTHREAD_CREATED_DETACHED时,线程退出时不需要join。

C.栈属性

线程使用栈地址和栈大小这两个属性来描述线程使用的栈。读/写线程栈属性的函数为

1
2
3
4
#include <pthread.h>
 
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);
int pthread_attr_setstack(const pthread_attr_t *attr, void *stackaddr, size_t *stacksize);

系统将参数中的线程栈地址定义为线程栈的最低地址空间。还可以使用以下函数单独设置线程的栈大小属性:

1
2
3
4
#include <pthread.h>
 
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

D.栈警戒区

栈警戒区在线程栈空间末尾之后,在线程空间溢出到警戒区时,线程将收到信号通知(一般是SIGSEGV)。栈警戒区的默认大小为PAGESIZE,如果自行定义了任何线程属性,但不修改线程的警戒区属性,则警戒区大小将被置零。

设置和修改栈警戒区属性的函数为:

1
2
3
4
#include <pthread.h>
 
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

E.未定义在pthread_attr_t中的线程属性

主要包括取消选项以及并发度选项。并发度描述了应用程序线程可映射的内核线程数,即对于用户程序,最多可以有多少个线程可以“同时”执行系统调用,在操作系统实现为一个内核线程只映射为一个用户进程时,并发度的提高有助于改善程序性能。

1
2
3
4
#include <pthread.h>
 
int pthread_getconcurrency(void);
int pthread_setconcurrency(int level);

在未设置并发度时,pthread_getconcurrency将返回0。pthread_setconcurrency的设置值不一定会被内核接受,而当参数为0时,将取消之前一次pthread_setconcurrency的设置,而让内核自行调度。

[更多...]


第十一章 线程 | 2008年11月18日

线程机制引入Unix家族的时间相对比较晚,标准化后称为POSIX Threads[1](以下简称线程),使用的库称为pthreads(7)。它提供了在一个进程中并行地执行多个任务的机制。有助于将一个程序清晰的分解成多个不同的独立部分,例如用一个线程专门处理信号,用一个线程专门处理异步事件,再用一个线程专门负责提供服务等等。同一个进程内的线程可以无限制的共享进程的数据资源。使用多线程也可以节省生成新进程时的系统开销,在一定程度上改善响应时间。

另一方面,线程对进程资源无障碍的共享也带来了并发和同步的问题,而且只要任一线程异常退出就会导致整个进程的崩溃。线程安全编程将增加程序的复杂性,同时也更难于调试,往往容易成为bug丛生的地方。另外由于现代的Unix-like系统(尤其是Linux)普遍采用了COW等减小进程生成开销的技术,使得很多时候线程的实现几乎已经没有作为“轻量级进程”的优势。而ESR在他的TAOUP一书中认为:“线程是那些进程生成昂贵、IPC功能薄弱的操作系统所特有的概念”[2],认为线程带来的好处与引入的麻烦相比得不偿失。

当然,这只是Unix老炮的一家之见。SMP架构以及基于网络的应用程序设计依然一直是线程编程的传统领域,前人有多年积累的实践经验。Programming with POSIX ThreadsMulti-threaded Programming Guide[3]等都是经典的POSIX线程编程参考资料。另外Gentoo创始人Daniel Robbins写的一篇文章[4]也是经典的POSIX线程开发的入门资料。

pthreads(7)库的实现采用类似面向对象的方式,线程对象、线程属性对象、线程同步原语对象等都采用了不透明的数据类型,并各自有其访问方法,不能使用其它原有的POSIX API去访问。APUE在其第一版中本来没有讲述线程编程的章节。Stevens去世后,后人在更新第二版时补充了两章多线程编程的章节。

本章讲述了线程的基本概念,包括线程的创建、撤销和同步原语,基本的线程数据类型及其访问方法等。下一章讲述线程各基本类型的属性及访问属性的方法,以及线程的交互。

1、线程ID类型的访问方法

取当前TID的函数是:

1
2
3
#include <pthread.h>
 
pthread_t pthread_self(void);

对两个TID进行比较(不应直接使用逻辑运算符)的函数是:

1
2
3
#include <pthread.h>
 
int pthread_equal(pthread_t tid1, pthread_t tid2);

[更多...]