您的位置:首页 > 运维架构 > Linux

Linux多线程编程--学习笔记--多线程简介

2014-03-17 17:32 351 查看
以下状态对于每个线程是唯一的。

■ 线程ID

■ 寄存器状态(包括PC和栈指针)

■ 栈

■ 信号掩码

■ 优先级

■ 线程专用存储

线程调度

POSIX标准指定了三种调度策略:

先入先出策略(SCHED_FIFO)

循环策略(SCHED_RR)

自定义策略(SCHED_OTHER)。

SCHED_FIFO是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR与FIFO相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO和SCHED_RR是对POSIX Realtime的扩展。SCHED_OTHER是缺省的调度策略。

线程调度提供了两个调度范围:进程范围(PTHREAD_SCOPE_PROCESS)和系统范围(PTHREAD_SCOPE_SYSTEM)。具有不同范围状态的线程可以在同一个系统甚至同一个程中共存。进程范围只允许这种线程与同一进程中的其他线程争用资源,而系统范围则允许此类线程与系统内的其他所有线程争用资源。实际上,从Solaris 9发行版开始,系统就不再区分这两个范围。

线程取消

一个线程可以请求终止同一个进程中的其他任何线程。目标线程(要取消的线程)可以延后取消请求,并在该线程处理取消请求时执行特定于应用程序的清理操作。通过pthread取消功能,可以对线程进行异步终止或延迟终止。异步取消可以随时发生,而延迟取消只能发生在所定义的点。延迟取消是缺省类型。

线程同步

使用同步功能,可以控制程序流并访问共享数据,从而并发执行多个线程。

共有四种同步模型:互斥锁、读写锁、条件变量和信号。

■ 互斥锁仅允许每次使用一个线程来执行特定的部分代码或者访问特定数据。

■ 读写锁允许对受保护的共享资源进行并发读取和独占写入。要修改资源,线程必须首先

获取互斥写锁。只有释放所有的读锁之后,才允许使用互斥写锁。

■ 条件变量会一直阻塞线程,直到特定的条件为真。

■ 计数信号量通常用来协调对资源的访问。使用计数,可以限制访问某个信号的线程数

量。达到指定的计数时,信号将阻塞。

POSIX标准指定了三种调度策略:

先入先出策略(SCHED_FIFO)

循环策略(SCHED_RR)

自定义策略(SCHED_OTHER)。

SCHED_FIFO是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR与FIFO相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO和SCHED_RR是对POSIX Realtime的扩展。SCHED_OTHER是缺省的调度策略。

基本线程编程

创建缺省线程

如果未指定属性对象,则该对象为NULL,系统会创建具有以下属性的缺省线程:

■ 进程范围

■ 非分离

■ 缺省栈和缺省栈大小

■ 零优先级

还可以用pthread_attr_init()创建缺省属性对象,然后使用该属性对象来创建缺省线程。

pthread_create语法

使用pthread_create(3C)可以向当前进程中添加新的受控线程。

int pthread_create(pthread_t *tid, const pthread_attr_t *tattr,

void*(*start_routine)(void *), void *arg);

使用具有必要状态行为的attr调用pthread_create()函数。start_routine是新线程最先执行

的函数。当start_routine返回时,该线程将退出,其退出状态设置为由start_routine返回的

值。

pthread_create()在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果

检测到以下任一情况,pthread_create()将失败并返回相应的值。

等待线程终止

pthread_join()函数会一直阻塞调用线程,直到指定的线程终止。

pthread_join语法

使用pthread_join(3C)等待线程终止。

int pthread_join(thread_t tid, void **status);

指定的线程必须位于当前的进程中,而且不得是分离线程。

实例

#include <pthread.h>
#include <stdio.h>

void *puts_char(void *arg)
{
puts("thread puts_char() join!");
pthread_exit(0);

}

int main()
{
pthread_t tid;
void * status;
void * arg;
pthread_create(&tid,NULL,puts_char,&arg);
pthread_join(tid,&status);
puts("thread puts_char() return!");
puts("thread main return !");
return 0;
}


分离线程

pthread_detach(3C)是pthread_join(3C)的替代函数,可回收创建时detachstate属性设置为

PTHREAD_CREATE_JOINABLE的线程的存储空间。

pthread_detach语法

int pthread_detach(thread_t tid);

pthread_detach()函数用于指示应用程序在线程tid终止时回收其存储空间。如果tid尚未终

止,pthread_detach()不会终止该线程。

pthread_detach返回值

pthread_detach()在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果

检测到以下任一情况,pthread_detach()将失败并返回相应的值。

EINVAL

描述:tid是分离线程。

ESRCH

描述:tid不是当前进程中有效的未分离的线程。

为线程特定数据创建键

单线程C程序有两类基本数据:局部数据和全局数据。对于多线程C程序,添加了第三类

数据:线程特定数据。线程特定数据与全局数据非常相似,区别在于前者为线程专有。

线程特定数据基于每线程进行维护。TSD(特定于线程的数据)是定义和引用线程专用数

据的唯一方法。每个线程特定数据项都与一个作用于进程内所有线程的键关联。通过使用

key,线程可以访问基于每线程进行维护的指针(void*)。

pthread_key_create语法

int pthread_key_create(pthread_key_t *key,

void (*destructor) (void *));

可以使用pthread_key_create(3C)分配用于标识进程中线程特定数据的键。键对进程中的

所有线程来说是全局的。创建线程特定数据时,所有线程最初都具有与该键关联的NULL

值。

使用各个键之前,会针对其调用一次pthread_key_create()。不存在对键(为进程中所有

的线程所共享)的隐含同步。

创建键之后,每个线程都会将一个值绑定到该键。这些值特定于线程并且针对每个线程单

独维护。如果创建该键时指定了destructor函数,则该线程终止时,系统会解除针对每线

程的绑定。

当pthread_key_create()成功返回时,会将已分配的键存储在key指向的位置中。调用方必

须确保对该键的存储和访问进行正确的同步。

使用可选的析构函数destructor可以释放过时的存储。如果某个键具有非NULL destructor

函数,而线程具有一个与该键关联的非NULL值,则该线程退出时,系统将使用当前的相关

值调用destructor函数。destructor函数的调用顺序不确定。

pthread_key_create返回值

pthread_key_create()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果

出现以下任一情况,pthread_key_create()将失败并返回相应的值。

EAGAIN

描述:key名称空间已经用完。

ENOMEM

描述:此进程中虚拟内存不足,无法创建新键。

删除线程特定数据键

使用pthread_key_delete(3C)可以销毁现有线程特定数据键。由于键已经无效,因此将释

放与该键关联的所有内存。引用无效键将返回错误。Solaris线程中没有类似的函数。

pthread_key_delete语法

int pthread_key_delete(pthread_key_t key);

如果已删除键,则使用调用pthread_setspecific()或pthread_getspecific()引用该键

时,生成的结果将是不确定的。

程序员在调用删除函数之前必须释放所有线程特定资源。删除函数不会调用任何析构函

数。反复调用pthread_key_create()和pthread_key_delete()可能会产生问题。如果

pthread_key_delete()将键标记为无效,而之后key的值不再被重用,那么反复调用它们就

会出现问题。对于每个所需的键,应当只调用pthread_key_create()一次。

pthread_key_delete返回值

pthread_key_delete()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果

出现以下情况,pthread_key_delete()将失败并返回相应的值。

EINVAL

描述:key的值无效。

设置线程特定数据

使用pthread_setspecific(3C)可以为指定线程特定数据键设置线程特定绑定。

pthread_setspecific语法

int pthread_setspecific(pthread_key_t key, const void *value);

pthread_setspecific返回值

pthread_setspecific()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果

出现以下任一情况,pthread_setspecific()将失败并返回相应的值。

ENOMEM

描述:虚拟内存不足。

EINVAL

描述:key无效。

注–设置新绑定时,pthread_setspecific()不会释放其存储空间。必须释放现有绑定,否

则会出现内存泄漏。

获取线程特定数据

请使用pthread_getspecific(3C)获取调用线程的键绑定,并将该绑定存储在value指向的

位置中。

pthread_getspecific语法

void *pthread_getspecific(pthread_key_t key);

获取线程标识符

请使用pthread_self(3C)获取调用线程的thread identifier。

pthread_self语法

pthread_t pthread_self(void);

比较线程ID

请使用pthread_equal(3C)对两个线程的线程标识号进行比较。

pthread_equal语法

int pthread_equal(pthread_t tid1, pthread_t tid2);

pthread_equal返回值

如果tid1和tid2相等,pthread_equal()将返回非零值,否则将返回零。如果tid1或tid2是

无效的线程标识号,则结果无法预测。

初始化线程

使用pthread_once(3C),可以在首次调用pthread_once时调用初始化例程。以后调用

pthread_once()将不起作用。

pthread_once语法

int pthread_once(pthread_once_t *once_control,void (*init_routine)(void));

#include <pthread.h>

pthread_once_t once_control = PTHREAD_ONCE_INIT;

int ret;

ret = pthread_once(&once_control,init_routine);

once_control参数用来确定是否已调用相关的初始化例程。

pthread_once返回值

pthread_once()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以

下情况,pthread_once()将失败并返回相应的值。

EINVAL

描述:once_control或init_routine是NULL。

停止执行线程

使用sched_yield(3RT),可以使当前线程停止执行,以便执行另一个具有相同或更高优先

级的线程。

sched_yield语法

int sched_yield(void);

#include <sched.h>

int ret;

ret = sched_yield();

sched_yield返回值

sched_yield()在成功完成之后返回零。否则,返回-1,并设置errno以指示错误状态。

ENOSYS

描述:本实现不支持sched_yield。

设置线程的优先级

请使用pthread_setschedparam(3C)修改现有线程的优先级。此函数对于调度策略不起作

用。

pthread_setschedparam语法

int pthread_setschedparam(pthread_t tid, int policy,

const struct sched_param *param);

#include <pthread.h>

pthread_t tid;

int ret;

struct sched_param param;

int priority;

/* sched_priority will be the priority of the thread */

sched_param.sched_priority = priority;

policy= SCHED_OTHER;

/* scheduling parameters of target thread */

ret = pthread_setschedparam(tid,policy,¶m);

pthread_setschedparam返回值

pthread_setschedparam()在成功完成之后返回零。其他任何返回值都表示出现了错误。如

果出现以下任一情况,pthread_setschedparam()函数将失败并返回相应的值。

EINVAL

描述:所设置属性的值无效。

ENOTSUP

描述:尝试将该属性设置为不受支持的值。

获取线程的优先级

pthread_getschedparam(3C)可用来获取现有线程的优先级。

pthread_getschedparam语法

int pthread_getschedparam(pthread_t tid, int policy,

struct schedparam *param);

#include <pthread.h>

pthread_t tid;

sched_param param;

int priority;

int policy;

int ret;

/* scheduling parameters of target thread */

ret = pthread_getschedparam (tid,&policy,¶m);

/* sched_priority contains the priority of the thread */

priority= param.sched_priority;

pthread_getschedparam返回值

pthread_getschedparam()在成功完成之后返回零。其他任何返回值都表示出现了错误。如

果出现以下情况,该函数将失败并返回对应的值。

ESRCH

描述:tid指定的值不引用现有的线程。

向线程发送信号

请使用pthread_kill(3C)向线程发送信号。

pthread_kill语法

int pthread_kill(thread_t tid, int sig);

#include <pthread.h>

#include <signal.h>

int sig;

pthread_t tid;

int ret;

ret = pthread_kill(tid,sig);

pthread_kill()将信号sig发送到由tid指定的线程。tid所指定的线程必须与调用线程在同

一个进程中。sig参数必须来自signal(5)提供的列表。

如果sig为零,将执行错误检查,但并不实际发送信号。此错误检查可用来检查tid的有效

性。

pthread_kill返回值

pthread_kill()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以

下任一情况,pthread_kill()将失败并返回相应的值。

EINVAL

描述:sig是无效的信号量。

ESRCH

描述:当前的进程中找不到tid。

访问调用线程的信号掩码

请使用pthread_sigmask(3C)更改或检查调用线程的信号掩码。

pthread_sigmask语法

int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);

#include <pthread.h>

#include <signal.h>

int ret;

sigset_t old,new;

ret = pthread_sigmask(SIG_SETMASK, &new,&old); /* set new mask */

ret = pthread_sigmask(SIG_BLOCK, &new,&old); /* blocking mask */

ret = pthread_sigmask(SIG_UNBLOCK, &new,&old); /* unblocking */

how用来确定如何更改信号组。how可以为以下值之一:

■ SIG_BLOCK。向当前的信号掩码中添加new,其中new表示要阻塞的信号组。

■ SIG_UNBLOCK。从当前的信号掩码中删除new,其中new表示要取消阻塞的信号组。

■ SIG_SETMASK。将当前的信号掩码替换为new,其中new表示新的信号掩码。

当new的值为NULL时,how的值没有意义,线程的信号掩码不发生变化。要查询当前已阻

塞的信号,请将NULL值赋给new参数。

除非old变量为NULL,否则old指向用来存储以前的信号掩码的空间。

pthread_sigmask返回值

pthread_sigmask()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现

以下情况,pthread_sigmask()将失败并返回相应的值。

EINVAL

描述:未定义how的值。

安全地Fork

pthread_atfork语法

int pthread_atfork(void (*prepare) (void), void (*parent) (void),

void (*child) (void) );

pthread_atfork返回值

pthread_atfork()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现

以下情况,pthread_atfork()将失败并返回相应的值。

ENOMEM

描述:表空间不足,无法记录Fork处理程序地址。

终止线程

请使用pthread_exit(3C)终止线程。

pthread_exit语法

void pthread_exit(void *status);

#include <pthread.h>

void *status;

pthread_exit(status); /* exit with status */

pthread_exit()函数可用来终止调用线程。将释放所有线程特定数据绑定。如果调用线程

尚未分离,则线程ID和status指定的退出状态将保持不变,直到应用程序调用

pthread_join()以等待该线程。否则,将忽略status。线程ID可以立即回收。有关线程分

离的信息,请参见第52页中的 “设置分离状态”。

pthread_exit返回值

调用线程将终止,退出状态设置为status的内容。

结束

线程可通过以下方法来终止执行:

■ 从线程的第一个(最外面的)过程返回,使线程启动例程。请参见pthread_create。

■ 调用pthread_exit(),提供退出状态。

■ 使用POSIX取消函数执行终止操作。请参见pthread_cancel()。

线程的缺省行为是拖延,直到其他线程通过"joining"拖延线程确认其已死亡。此行为与非

分离的缺省pthread_create()属性相同,请参见pthread_detach。join的结果是joining线

程得到已终止线程的退出状态,已终止的线程将消失。

有一个重要的特殊情况,即当初始线程(即调用main()的线程)从main()调用返回时或调

用exit()时,整个进程及其所有的线程将终止。因此,一定要确保初始线程不会从main()

过早地返回。

请注意,如果主线程仅仅调用了pthread_exit,则仅主线程本身终止。进程及进程内的其

他线程将继续存在。所有线程都已终止时,进程也将终止。

取消线程

取消操作允许线程请求终止其所在进程中的任何其他线程。不希望或不需要对一组相关的

线程执行进一步操作时,可以选择执行取消操作。

取消线程的一个示例是异步生成取消条件,例如,用户请求关闭或退出正在运行的应用程

序。另一个示例是完成由许多线程执行的任务。其中的某个线程可能最终完成了该任务,

而其他线程还在继续运行。由于正在运行的线程此时没有任何用处,因此应当取消这些线

程。

取消点

仅当取消操作安全时才应取消线程。pthreads标准指定了几个取消点,其中包括:

■ 通过pthread_testcancel调用以编程方式建立线程取消点。

■ 线程等待pthread_cond_wait或pthread_cond_timedwait(3C)中的特定条件出现。



■ 被sigwait(2)阻塞的线程。

■ 一些标准的库调用。通常,这些调用包括线程可基于其阻塞的函数。有关列表,请参见

cancellation(5)手册页。

缺省情况下将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功

能,则会导致延迟所有的取消请求,直到再次启用取消请求。

放置取消点

执行取消操作存在一定的危险。大多数危险都与完全恢复不变量和释放共享资源有关。取

消线程时一定要格外小心,否则可能会使互斥保留为锁定状态,从而导致死锁。或者,已

取消的线程可能保留已分配的内存区域,但是系统无法识别这一部分内存,从而无法释放

它。

标准C库指定了一个取消接口用于以编程方式允许或禁止取消功能。该库定义的取消点是

一组可能会执行取消操作的点。该库还允许定义取消处理程序的范围,以确保这些处理程

序在预期的时间和位置运行。取消处理程序提供的清理服务可以将资源和状态恢复到与起

点一致的状态。

必须对应用程序有一定的了解,才能放置取消点并执行取消处理程序。互斥肯定不是取消

点,只应当在必要时使之保留尽可能短的时间。

请将异步取消区域限制在没有外部依赖性的序列,因为外部依赖性可能会产生挂起的资源

或未解决的状态条件。在从某个备用的嵌套取消状态返回时,一定要小心地恢复取消状

态。该接口提供便于进行恢复的功能:pthread_setcancelstate(3C)在所引用的变量中保留

当前的取消状态,pthread_setcanceltype(3C)以同样的方式保留当前的取消类型。

在以下三种不同的情况下可能会执行取消操作:

■ 异步

■ 执行序列中按标准定义的各个点

■ 调用pthread_testcancel()时

缺省情况下,仅在POSIX标准可靠定义的点执行取消操作。

无论何时,都应注意资源和状态恢已复到与起点一致的状态。

取消线程

请使用pthread_cancel(3C)取消线程。

pthread_cancel语法

int pthread_cancel(pthread_t thread);

#include <pthread.h>

pthread_t thread;

int ret;

ret = pthread_cancel(thread);

取消请求的处理方式取决于目标线程的状态。状态由以下两个函数确定

:pthread_setcancelstate(3C)和pthread_setcanceltype(3C)。

pthread_cancel返回值

pthread_cancel()在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现

以下情况,该函数将失败并返回对应的值。

ESRCH

描述:没有找到与给定线程ID相对应的线程。

启用或禁用取消功能

请使用pthread_setcancelstate(3C)启用或禁用线程取消功能。创建线程时,缺省情况下线

程取消功能处于启用状态。

pthread_setcancelstate语法

int pthread_setcancelstate(int state, int *oldstate);

#include <pthread.h>

int oldstate;

int ret;

/* enabled */

ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);

/* disabled */

ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

pthread_setcancelstate返回值

pthread_setcancelstate()在成功完成之后返回零。其他任何返回值都表示出现了错误。如

果出现以下情况,pthread_setcancelstate()函数将失败并返回相应的值。

EINVAL

描述:状态不是PTHREAD_CANCEL_ENABLE或PTHREAD_CANCEL_DISABLE。

设置取消类型

使用pthread_setcanceltype(3C)可以将取消类型设置为延迟或异步模式。

pthread_setcanceltype语法

int pthread_setcanceltype(int type, int *oldtype);

#include <pthread.h>

int oldtype;

int ret;

/* deferred mode */

ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

/* async mode*/

ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);

创建线程时,缺省情况下会将取消类型设置为延迟模式。在延迟模式下,只能在取消点取

消线程。在异步模式下,可以在执行过程中的任意一点取消线程。因此建议不使用异步模

式。

pthread_setcanceltype返回值

pthread_setcanceltype()在成功完成之后返回零。其他任何返回值都表示出现了错误。如

果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:类型不是PTHREAD_CANCEL_DEFERRED或PTHREAD_CANCEL_ASYNCHRONOUS。

创建取消点

请使用pthread_testcancel(3C)为线程建立取消点。

pthread_testcancel语法

void pthread_testcancel(void);

pthread_testcancel();

当线程取消功能处于启用状态且取消类型设置为延迟模式时,pthread_testcancel()函数

有效。如果在取消功能处于禁用状态下调用pthread_testcancel(),则该函数不起作用。

请务必仅在线程取消操作安全的序列中插入pthread_testcancel()。除通过

pthread_testcancel()调用以编程方式建立的取消点以外,pthread标准还指定了几个取消点。

pthread_testcancel返回值

pthread_testcancel()没有返回值。

将处理程序推送到栈上

使用清理处理程序,可以将状态恢复到与起点一致的状态,其中包括清理已分配的资源和

恢复不变量。使用pthread_cleanup_push(3C)和pthread_cleanup_pop(3C)函数可以管理清

理处理程序。

在程序的同一词法域中可以推送和弹出清理处理程序。推送和弹出操作应当始终匹配,否

则会生成编译器错误。

pthread_cleanup_push语法

请使用pthread_cleanup_push(3C)将清理处理程序推送到清理栈(LIFO)。

void pthread_cleanup_push(void(*routine)(void *), void *args);

#include <pthread.h>

/* push the handler "routine" on cleanup stack */

pthread_cleanup_push (routine,arg);

pthread_cleanup_push返回值

pthread_cleanup_push()没有返回值。

从栈中弹出处理程序

请使用pthread_cleanup_pop(3C)从清理栈中弹出清理处理程序。

pthread_cleanup_pop语法

void pthread_cleanup_pop(int execute);

#include <pthread.h>

/* pop the "func" out of cleanup stack and execute "func" */

pthread_cleanup_pop (1);

/* pop the "func" and DONT execute "func" */

pthread_cleanup_pop (0);

如果弹出函数中的参数为非零值,则会从栈中删除该处理程序并执行该处理程序。如果该

参数为零,则会弹出该处理程序,而不执行它。

线程显式或隐式调用pthread_exit(3C)时,或线程接受取消请求时,会使用非零参数有效地

调用pthread_cleanup_pop()。

pthread_cleanup_pop返回值

pthread_cleanup_pop()没有返回值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: