八月 18, 2010

POSIX线程基本概念

Written by

线程编程在smp体系结构处理并发时会被提及的比较多。它可以实现并发多道操作,常被称为轻量级的进程,因为它可以共享进程资源,省去了多进程切换时内核的上下文切换所用的花销。
多数Linux编程教材里面对POSIX线程的介绍占的篇幅并不多。Linux的fork对多进程的有较好的优化技术,而__clone系统调用相当于使 用进程的方法实现线程。进程的另一个好处是稳定性,进程在退出时操作系统自动回收资源,而共享资源的线程需要程序员自己设计释放资源,积累的错误可能会使 得程序崩溃。
线程和进程如何使用的关系,在不少unix尤其是linux编程的相关社区依然是经久不息的话题。这篇文章大致说了一下linux编程为什么多用进程而少用线程。而Gentoo创始人Daniel Robbins的这一篇相当好的线程入门文章。还有这篇
我只是初学者,下面是我的学习笔记。

1、__clone函数调用

#include <sched.h>
int __clone(int (*fn)(void *fnarg), void *child_stack, int flags, void *arg);

Linux可以通过__clone函数调用创建子进程来实现线程,__clone同时也可以实现fork。__clone的第一个参数是执行子进程时的调 用函数指针,这个函数用一个void类型的指针作为参数,返回int整型;第二个参数为为子进程分配的堆栈指针,如果想让操作系统为它来分配,则设置为 NULL,第三个为CLONE标志,作为调用选项,flags包括:CLONE_VM(是否设置则共享内存镜像,否则就类似于fork,子进程拷贝一份父 进程的副本)、CLONE_FS(是否共享相同的根文件系统,当前动作目录和umask)、CLONE_FILES(是否共享文件描述符,否则子进程依然 会继承父进程的文件描述符表,但它们的读/写/打开/关闭操作互不影响)、CLONE_SIGHAND(是否共享设置在父进程上的信号处理器,否则子进程 自己拷贝一份副本,它们的sigaction互不影响)、CONE_PID(子进程是否继续使用父进程的pid)。最后一个参数arg可以设置为传递 给*fn子函数的参数,注意类型是void,这意味着它是什么类型可以自己选择,包括NULL。

2、POSIX线程库的pthread API

pthread_create:创建一个线程

#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

第一个参数为要创建的线程结构pthread_t,第二个参数为线程的相关属性,第三个参数是该线程要执行的函数的指针,第四个则是传递给*start_routine的参数,可以自行选择它的取值和用法。
pthread_create执行成功返回0,并把tid线程标识符存放到pthread_t结构中;否则返回一个非零值并设置errno。

pthread_exit:终止当前线程

#include <pthread.h>
void pthread_exit(void *retval);

pthread_exit退出当前线程,退出之前将调用pthread_cleanup_push。它在线程的最上层函数的最后是被隐式调用的,这时可以 加一个retval参数显式调用之,以供pthread_join参考。

pthread_join:挂起当前线程

#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);

pthread_join把当前线程挂起直到指定的线程th终止,thread_return为th终止时的参数,如果没有显式指定,则为NULL。

pthread_cancel:撤消一个线程

#include <pthread.h>
int pthread_cancel(pthread_t thread);
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
void pthread_testcancel(void);

pthread_cancel用于撤消线程thread。
pthread_setcancelstate可以用于设置当前线程的撤消状态,包括PTHREAD_CANCEL_ENABLE和 PTHREAD_CANCEL_DISABLE,允许或者忽略撤消。在一些关键操作中可以设置它以避免被另外一个线程用pthread_cancel来撤 消。
pthread_setcanceltype设置当前线程的撤消类型,包括PTHREAD_CANCEL_ASYNCHRONOUS立即撤消、 PTHREAD_CANCEL_DEFERRED延迟至撤消点。可以通过调用pthread_testcancel或者使用条件变量来设置撤消点。

3、线程属性

线程属性包括:
detachstate ——分离或者切入状态,它的值有PTHREAD_CREATE_JOINABLE(默认值), PTHREAD_CREATE_DETACHED;
schedpolicy ——调度策略,取值有SCHED_OTHER(默认值), SCHED_FIFO, SCHED_FIFO;
schedparam ——跟调度策略有关;
inheritsched ——PTHREAD_EXPLICIT_SCHED(默认值), PTHREAD_INHERIT_SCHED;
scope —— 时间片,取值有PTHREAD_SCOPE_SYSTEM(默认值),每个线程一个系统时间片,PTHREAD_SCOPE_PROCESS线程共享系统时间片。
线程属性对象的类型为pthread_attr_t,通过pthread_attr_xxxxx函数族操作线程属性对象。

4、pthread cleanup宏

pthrad cleanup宏主要用于处理线程的退出状态,pthread_exit和pthread_join等可以用它来作参数,包括

#include <pthread.h>
void pthread_cleanup_push(void (*routine), (void *), void *arg);
void pthread_cleanup_pop(int execute);
void pthread_cleanup_push_defer_np(void (*routine), (void *), void *arg);
void pthread_cleanup_pop_restore_np(int execute);

这些宏主要用于线程结束时释放有关资源,用pthread_exit调用pthread_cleanup_push进行处理时,要注意用完后要用对应的pthread_cleanup_pop把它从堆栈弹出。

5、互斥mutex

由于线程是共享资源的,所以互斥就显得相当重要。互斥提供了对互斥对象上锁的方法以获得对此资源的独占,其它企图对此互斥加锁的线程则会被阻塞而挂起,直到对资源加锁的线程解锁为止。
在使用线程编程时,不必处处都使用互斥,否则会失去并发线程的意义,例如,在对使用只能串行单独访问的资源下方使用互斥。
互斥对象在pthead.h中定义为pthread_mutex_t。它的系统调用主要包括:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutex_init创建一个互斥对象,并用指定的属性初始化这个互斥对象,初始化属性包 括:PTHREAD_MUTEX_INITIALIZER创建快速互斥,这种互斥执行简单的加锁和解锁,在加锁后阻塞另一个要给它上锁的线 程;PTHREAD_RECURSIVE_MUTEX_INITIALIZER创建递归互斥,这种互斥将给加锁计数,解锁时需要调用同样次数的 pthread_mutex_unlock;
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER创建检错互斥,这种互斥在被锁上后会向试图给它加锁的线程返回一个EDEADLK错误代码而不阻塞。

pthread_mutex_lock和pthread_mutex_unlock则为加锁和解锁指定的互斥对 象,pthread_mutex_trylock和pthread_mutex_lock的不同是如果互斥对象已上锁不会被阻塞而是返回一个EBUSY错 误代码,pthread_mutex_destory析构指针mutex并释放相关资源。这几个调用成功都返回0,失败返回一个非零的错误代码。

6、条件变量

线程用条件变量对象来阻塞自己以等待某个指定条件发生,条件对象在<pthread.h>中定义为pthread_cond_t。

#include <pthead.h>
pthread_cond_t cond = pthread_cond_initializer;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);

pthread_cond_init创建一个指向条件变量的指针,并用一个参数初始化该变量,这个参数在Linux下被忽略而使用 PTHREAD_COND_INITIALIZER,而pthread_cond_t则析构该指针并释放相关系统资源。
线程在锁定一个互斥后,如果需要某个条件到来,则应调用等待所需条件,pthread_cond_wait将检查指定的条件,没有则把当前线程挂起,并解 锁指定的互斥,在条件到达后重新锁定该互斥,并唤醒被挂起的线程。pthread_cond_timedwait则为计时等待,使用的abstime这个 参数为和兼容time()返回值的绝对时间,即以经典的UNIX纪元时间1970-01-01起至今的秒数。如果这个时间前等待条件未发生,则结束等待并 返回ETIMEOUT代码。这两个函数都将被设置为撤消点。

pthread_cond_signal和pthread_cond_broadcast用在一个线程解锁互斥后唤醒等待条件cond的线程,不同之处在 于前者按顺序唤醒第一个进入等待队列的线程,后者唤醒等待该条件的所有线程。

 

Category : C/C++KernelLinux/Unix其他

Tags :

发表评论

电子邮件地址不会被公开。

Proudly powered by WordPress and Sweet Tech Theme