现代操作系统都支持多线程并发运行,尤其在多核cpu上,可以真正实现并行运行,而且多线程编程也利于code的设计,优化架构。Linux系统编程手册29章开始介绍了线程的概念。Linux系统提供了两种线程的实现:LinuxThreads和NPTL(Native POSIX Threads Library),第一种比较古老,现在已经基本不支持,现在系统的实现是使用NPTL。
除了linux系统提供的线程支持外,标准库利用linux系统的支持,提供更高级的线程操作,比如async(),future,thread,promise等机制,以供开发者灵活运用。下面对各种实现进行总结。
注意一点,不要将线程和信号混合使用,多线程程序应该避免使用信号。如果非要使用的话,最简单的方法是所有线程多阻塞信号,单独创建一个专门的线程来调用sigwait()函数(或者其他类似函数)来接收处理信号。

1.Linux系统的线程实现:

1.1 在Linux中,通过clone系统调用来实现线程。同一个进程的不同线程之间共享全局变量,堆变量,进程ID(pid)和父进程ID(ppid),进程组ID和会话ID,用户ID和组ID,打开的文件描述符,信号(signal)处置,fcntl创建的记录锁,文件系统的相关信息,cpu消耗和资源消耗,nice值。
1.2 线程独有的信息为:线程ID(tid),信号掩码,线程特有的数据,备选信号栈,errno变量,浮点型环境,实时调度策略和优先级,cpu亲和力,能力,栈。
1.3在Linux平台上编译调用了Pthread API的程序时,需要设置cc -pthread编译选项来支持。
1.4线程操作函数:

#include <pthread.h>
/*return 0 on success, or a positive error number on error.
新线程会调用嗲有参数arg的函数start(start(arg))开始执行。attr设置为null,会使用默认属性。*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *), void *arg);
/*线程id会返回给pthread_create()的调用者,一个线程可以通过pthread_self()来获取自己的线程*/
pthread_t pthread_self(void);
/*检查两个线程id是否相同,return nonzero value if t1 and t2 are equal, otherwise 0.不能直接用==来检测,因为不同的系统,该类型有不同的实现。Linux中的实现可以用(long)强制转化为%ld数据。*/
int pthread_equal(pthread_t t1, pthread_t t2);
/*retval若非空,则会保存线程终止时返回值的拷贝,该返回值亦即线程调用return或pthread_exit()时所指定的值。如果传入一个已经连接过的线程id,则结果未知。创建的线程要嘛join,要嘛detach,不然,该线程退出后会称为僵尸线程,占用系统资源。join函数会一直阻塞,等待该join的线程退出才返回。*/
int pthread_join(pthread_t thread, void **retval);
/*如果对线程的运行结果不关心,则可以detach,一旦detach,就不能对其调用join了,系统在线程终止时会自动清除。*/
int pthread_detach(pthread_t thread);
/*线程退出有两种方法,一种线程的main函数执行完毕,另外一种可以调用exit函数,join函数会得到exit时设置的retval。*/
void pthread_exit(void *retval);
/*其他线程可以向某一线程发送取消请求,请求其立即退出,比如一旦某个线程发生错误,会要求其他线程一起退出,就可以调用该接口,发出请求后,该函数立即返回,不会等待目标线程的退出。.return 0 on success, or a positive number on error.目标线程会发生什么,取决于下面介绍的线程取消状态和类型。*/
int pthread_cancel(pthread_t thread);
/*该函数设置取消状态,PTHREAD_CANCEL_DISABLE:线程不可取消,cancel请求会被挂起,直到状态变为PTHREAD_CANCEL_ENABLE. oldstate会返回原来的state。*/
int pthread_setcanclestate(int state, int *oldstate);
/*如果线程可以取消(enable),则会看type:PTHREAD_CANCEL_ASYNCHRONOUS :异步取消,就是会在任何时间点取消线程,很少用。 PTHREAD_CANCEL_DEFERED :延后取消,直到到达取消点才会取消,这个是默认type。取消点就是很多系统函数,大部分block的函数都是取消点。被取消的函数被调用join会返回一个特殊值:PTHREAD_CANCELED.*/
int pthread_setcanceltype(int type, int *oldtype);
/*如果code中没有上面所说的取消点,则可以设置取消点,利用下面函数*/
void pthread_testcancel(void);
/*如果某个线程被突然取消,则可能很多资源没有被释放,则可以设置清理函数,线程被取消后会自动调用.
最先被push的最后执行,如果程序执行到某个地方不需要执行某个清理函数,则可以pop出来,如果pop参数为0,则该清理函数不会执行,如果参数大于0,则pop出来的清理函数还是会执行。因为这两个函数可能为宏定义,则必须在同一个作用域上。如果线程是调用pthread_exit退出的,则会执行未pop的清理函数,如果是正常return,则不会执行清理函数。code例子可以参考linux系统编程手册P557*/
void pthread_cleanup_push(void (*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);
/*信号动作属于进程层面,某个线程设置的信号处理函数就是整个进程的信号处理函数,当某个进程收到信号可以被该进程的任意线程获取,并调用指定的信号处理函数。但有些信号可以属于线程例如可以用下面函数向同一个进程的线程发送信号。return 0 on success, on error, it returns an positive error number.*/
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
/*信号掩码也是属于线程的,某线程创建的子线程会继承父线程的信号掩码。
return 0 on success, or a positive error number on error.
*/
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

一般不要将信号与线程混在一起使用,如果需要使用,建议在创建任何其他线程之前,由主线程阻塞这些信号,后续创建的每个线程都会继承主线程的信号掩码的拷贝,然后再创建一个专用线程,修改其sigmask,enable信号接收,调用sigwait()等函数来接收信号并处理。

2.标准库std的线程实现:

标准库利用系统的底层实现,提供了一些高级接口,提供一些灵活的异步调用。总结如下:
2.1 高级接口:async() 和 Future
std::async() : 让一个可被调用的object(包括函数,成员函数等)在后台运行,成为一个独立线程。
class std::future<> 允许等待线程结束并获取其结果(一个返回值或者也许是一个异常)

Logo

更多推荐