linux编程:pthread

1.1 线程数据类型(pthread data type)

这里仅仅列出常用的线程数据类型。 关于这里的数据类型的定义,mutex:互斥,attr(attribution):属性,cond:条件,t:type。

数据类型描述
pthread_t线程ID
pthread_mutex_t互斥对象(Mutex)
pthread_mutexattr_t互斥属性对象
pthread_cond_t条件变量(condition variable)
pthread_condattr_t条件变量的属性对象
pthread_key_t线程特有数据的键(key)
pthread_once_t一次性初始化控制上下文(control context)
pthread_attr_t线程的属性对象

1.2 线程和errno

一般来说,errno是一个全局整型变量,如果线程调用的函数通过全局errno返回错误时,会与其他发起函数条用并检查errno的线程混淆在一起,也就是说,这将引发竞争条件(race condition)。因此,在多线程程序中,每个线程都有属于自己的errno。

1.3 pthreads函数的返回值

所有的pthread函数均以返回0表示成功,返回一正值表示失败。由于多线程程序对errno的每次引用都会带来函数调用的开销,报错函数可能需要重构。

1.4 pthrtead相关函数

1.4.1 创建线程

函数pthread_create()用来创建一条新的线程。新线程通过调用带有参数arg的函数start,也就是start(arg),而开始执行。调用pthread_create()的线程会继续执行该调用之后的语句。

#include <pthread.h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start)(void *),void *arg);
// 返回值:0为成功;正值出现错误

函数参数说明:
thread:在pthread_create()返回前,会将线程唯一标识保存到thread中。
attr:指向pthread_attr_t对象的指针,该对象制定了新线程的各种属性。如果设置为NULL,name创建新线程时将采用默认属性。
start:新线程调用函数的名字。
arg:start的参数列表,arg指向一个全局或者堆变量,也可以设置为Null,如果需要传递多个参数,可以将arg指向一个结构。

1.4.2 终止线程

可以采用下面的方式终止线程:

  • 线程start函数执行return语句并返回指定值。
  • 线程调用pthread_exit()。
  • 调用pthread_cancel()取消线程。
  • 任意线程调用了exit(),或者主线程执行了return语句(在main函数中)。

pthread_exit()函数将终止调用线程,且其返回值可以通过调用pthread_join()来获取。调用pthread_exit()相当于在新线程函数start()中执行return,不同之处在于,可在线程start()函数所调用的任意函数中调用pthread_exit()。

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

函数参数说明:
retval:制定线程的返回值。

1.4.3 线程ID

进程内部的每一个线程都有唯一标识,叫线程ID。线程ID会返回给pthread_create()的调用者,一个线程可以通过pthread_self()获取自己的线程ID。

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

函数pthread_equal()可检查两个线程的ID是否相同。

#include <pthread.h>
int pthread_equal(pthread_t t1,pthread_t t2);
//返回值:如果t1和t2相等就返回非零值,否则返回0

函数参数说明:
没什么需要特别注意。

1.4.4 连接(joining)已终止的线程

函数pthread_join()等待由thread标识的线程终止,如果线程已经终止,该函数会立即返回。

#include <pthread.h>
int pthread_join(pthread_t thread,void **retval);
//返回值:返回0表示成功,返回正数表示失败

函数参数说明:
若retval为一非空指针,将会保存线程终止时返回值的拷贝,该返回值即线程调用return或pthread_exit()时所制定的值。

函数实例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#define EXIT_SUCCESS 0
static void * threadfunc(void *arg);

int main(void)
{
    pthread_t t1;
    void *res=NULL;
    int s=0;

    s=pthread_create(&t1,NULL,threadfunc,"hello world\n");

    if(s!=0)
        perror("pthread create failed\n");
    printf("Message from main()\n");
    s=pthread_join(t1,&res);
    if(s!=0)
        perror("pthread join failed\n");
    printf("Thread returned %ld",(long)res);
    exit(EXIT_SUCCESS);
}

static void * threadfunc(void *arg)
{
    char *s = (char *) arg;
    printf("%s",s);
    return (void *) strlen(s);
}

执行结果:

Message from main()
hello world

1.4.5 线程分离

想要在线程终止时能,够被自动清理并被移除。在这种情况下,可以调用pthread_detach()并向thread参数传入制定线程的表示符,将该线程标记为分离状态。默认情况下,线程是可连接的(joinable),也就是说,当线程退出时,其他线程可以通过滴啊用pthread_join()获取其返回状态。

#include <pthread.h>
int pthread_detach(pthread_t thread);
//返回值:如果成功就返回0,否则返回一个正数

函数说明:
使用pthread_detach(),线程可以自行分离:

pthread_detach(pthread_self());

一旦线程处于分离状态,就不能使用pthread_join获取其状态,也无法返回“可连接”状态。

1.4.6 线程属性

线程属性为一个结构体。详细例如以下:

typedef struct
{      
	int        detachstate;       //线程的分离状态
	int        schedpolicy;    //线程调度策略
	structsched_param  schedparam;  //线程的调度參数
	int        inheritsched;     //线程的继承性
	int        scope;         //线程的作用域
	size_t     guardsize;     //线程栈末尾的警戒缓冲区大小
	void*      stackaddr;      //线程栈的位置
	size_t     stacksize;        //线程栈的大小
}pthread_attr_t;

使用默认属性初始化线程属性*ATTR

int pthread_attr_init (pthread_attr_t *__attr)

获取分离状态属性

int pthread_attr_getdetachstate (const pthread_attr_t *__attr,
							int *__detachstate);

设置分离状态属性

int pthread_attr_setdetachstate (pthread_attr_t *__attr,
							int __detachstate);

**获取 为堆栈溢出保护创建的 保护区域 的 大小 **

int pthread_attr_getguardsize (const pthread_attr_t *__attr,
				      size_t *__guardsize);

设置 为堆栈溢出保护创建的 保护区域 的 大小

int pthread_attr_setguardsize (pthread_attr_t *__attr,
				      size_t __guardsize);

**在PARAM中返回ATTR的调度参数 **

int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr,
				       struct sched_param *__restrict __param);

根据PARAM,在*ATTR中,设置调度参数(优先级等)

int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr,
				       const struct sched_param *__restrict);

返回调度策略

int pthread_attr_getschedpolicy (const pthread_attr_t *__restrict
					__attr, int *__restrict __policy);

设置调度策略

int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy);

返回调度继承

int pthread_attr_getinheritsched (const pthread_attr_t *__restrict
					 __attr, int *__restrict __inherit);

设置调度继承

int pthread_attr_setinheritsched (pthread_attr_t *__attr,
					 int __inherit);

还有很多相关函数,使用的时候去看pthread.h。

1.5 线程同步

1.5.1 基础知识

为避免线程更新共享变量时所出现的资源共享冲突问题,必须使用互斥量(mutex)来确保同时仅有一个线程可以访问某项共享资源,或者说,可以使用互斥量来保证对任意共享资源的原子访问。
互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。一旦线程锁定互斥量,随即成为该互斥量的所有者。只有所有者才能给互斥量解锁。
静态分配的互斥量
互斥量既可以静态分配也可以malloc动态分配。互斥量时属于pthread_mutex_t类型的变量,在使用之前必须对其进行初始化。对于静态分配的互斥量,可按照如下初始化:

pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;

1.5.2 加锁和解锁互斥量

初始化之后,互斥量处于未锁定状态。函数pthread_mutex_lock()可以锁定某一组互斥量,函数pthread_mutex_unlock()则可以将一个互斥量解锁。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//返回值:返回0表示成功,返回正值表示出错。

函数说明
如果互斥量当前处于未锁定状态,pthread_mutex_lock()函数调用将锁定互斥量并立即返回。如果其他线程已经锁定了这一互斥量,那么pthread_mutex_lock()函数调用会一直阻塞,直至该互斥量被解锁,到那时,调用将速冻互斥量并返回。
函数pthread_mutex_unlock()将解锁之前已经被线程锁定的互斥量。一下行为均属错误:对处于未锁定状态的互斥量进行解锁,或者解锁由其他线程锁定的互斥量。
示例程序:

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

static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static void * threadfunc(void *arg);
int main(void)
{
    pthread_t t1,t2;
    int loops=0,s=0;

    loops = 100;
    s=pthread_create(&t1,NULL,threadfunc,&loops);
    if(s!=0)
        perror("pthread_create");
    s=pthread_create(&t2,NULL,threadfunc,&loops);
    if(s!=0)
        perror("pthread_create");
    s=pthread_join(t1,NULL);
    if(s!=0)
        perror("pthread_join");
    s=pthread_join(t2,NULL);
    if(s!=0)
        perror("pthread_join");
    printf("glob=%d\n",glob);
    exit(0);
}

static void * threadfunc(void *arg)
{
    int loops = *((int *)arg);
    int loc=0,j=0,s=0;

    for(j=0;j<loops;j++)
    {
        s=pthread_mutex_lock(&mtx);
        if(s!=0)
            perror("pthread_mutex_lock");
        loc = glob;
        loc++;
        glob=loc;

        s=pthread_mutex_unlock(&mtx);
        if(s!=0)
            perror("pthread_mutex_unlock");
    }
    return NULL;
}

1.5.3 互斥量的性能

在通常情况下,线程会花费更多时间去做其他工作,对互斥量的加锁和解锁操作相对少得多,因此使用互斥量对于大部分程序的西能并无明显影响。

1.5.4 互斥量的死锁

当超过一个线程加锁同一组互斥量时,就有可能发生死锁。比如,下面所示,其中每个线程都成功地锁住一个互斥量,接着试图对已为另一线程锁定的互斥量加锁,两个线程将无期限地等待下去。

线程A线程B
1pthread_mutex_lock(mutex1)pthread_mutex_lock(mutex2)
2pthread_mutex_lock(mutex2)pthread_mutex_lock(mutex1)

解决方案:定义互斥量的层级关系,当多个线程对一组互斥量操作时,总是以相同顺序对该组互斥量进行锁定。

1.5.5 动态初始化互斥量

静态初始值PTHREAD_MUTEX_INITIALIZER只能对经由静态分配且携带默认属性的互斥量进行初始化。其他情况下,必须调用pthread_mutex_init()对互斥量进行动态初始化

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
//返回值:返回0表示成功,否则,返回一个正数

函数参数说明:
attr:指向pthread_mutexattr_t类型对象指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性。若将attr参数设置为NULL,则该互斥量的各种属性会取默认值。

在如下情况中,必须使用函数pthread_mutex_init(),而非静态初始化互斥量。

  • 动态分配于堆中的互斥量。
  • 互斥量是在栈中分配的自动变量。
  • 初始化经由静态分配,且不使用默认属性的互斥量。

当不再需要经由自动或动态分配的互斥量时,应使用pthread_mutex_destroy()将其销毁。

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

1.6 通知状态的改变:条件变量(condition variable)

条件变量允许一个线程就某个共享变量(或其他共享资源)的状态变化通知其他线程,并让其他线程等待(堵塞于)这一通知。

1.6.1 由静态分配的条件变量

使用条件变量前必须对其进行初始化:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

1.6.2 通知和等待条件变量

条件变量的主要操作是发送信号(signal)和等待(wait)。发送信号操作即通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变。等待操作是指在收到一个通知前一直处于阻塞状态。
函数pthread_cond_signal()和pthread_cond_broadcast()均可针对由参数cond所制定的条件变量而发送信号。pthread_cond_wait()函数将阻塞这一线程,直至收到条件变量cond的通知。

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcat(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
//返回值:返回0表示成功,否则,返回一个正数

函数pthread_cond_signal()保证唤醒至少一条遭到阻塞的线程,而函数pthread_cond_broadcast()则会唤醒所有遭到阻塞的线程。
函数pthread_cond_timewait()与函数pthread_cond_wait()几近相同,但pthread_cond_timewait()可以指定一个线程等待条件变量通知休眠时间的上限。

#include <pthread.h>
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex_t *mutex,
												const struct timespec *abstime);
//返回值:返回0表示成功,否则,返回一个正数

1.6.3 经由动态分配的条件变量

使用函数pthread_cond_init()对条件变量进行动态初始化。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
//返回值:返回0表示成功,否则,返回一个正数

函数参数说明:
cond:表示将要初始化的目标条件变量。
attr:attr指向 pthread_condattr_t类型对象
当不在需要一个经由自动或动态分配的条件变量时,应调用pthread_cond_destroy()函数予以销毁。

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
//返回值:返回0表示成功,否则,返回一个正数

1.7 线程安全和每线程存储

1.7.1 线程安全

若函数可同时供多个线程安全调用,则称之为线程安全函数;反之,如果函数不是线程安全的,则不能并发调用。

1.7.2 一次性初始化

函数pthread_once()可以确保无论有多少线程对pthread_once()调用多少次,也只会执行一次由init指向的调用者定义函数。

#include <pthread.h>
int pthread_once(pthread_once_t *once_control,void (*init)(void));
//返回值:返回0表示成功,否则,返回一个正数

init函数没有任何参数,形式如下:

#include <pthread.h>
void init(void)
{
}

参数once_control必须是一指针,指向初始化为PTHRAED_ONCE_INIT的静态变量。

pthread_once_t once_var = PTHREAD_ONCE_INIT;

1.7.3 线程特有数据API

调用pthread_key_create()函数为线程特有数据创建一个新键,并通过key所指向的缓冲区返回给调用者。因为进程中的所有线程都可使用返回的键,所以参数key应指向一个全局变量。

#include <pthread.h>
int pthread_key_create(pthread_key_t *key,void (*destructor)(void *));
//返回值:返回0表示成功,否则,返回一个正数

函数参数说明:
destructor:指向一个自定义函数,格式如下:

void dest(void *value)
{

}

函数pthread_setspecific()要求Pthreads API将value的副本存储于一数据结构中,并将value于调用线程以及key相关联(key由之前对pthread_key_create()的调用返回)。pthread_getspecific()函数执行的操作与之相反,返回之前与本线程及给定key相关的值(value)。

#include <pthread.h>
int pthread_setspecific(pthread_key_t key,const void *value);
//返回值:返回0表示成功,否则,返回一个正数

void *pthread_getspecific(pthread_key_t key);

1.8 线程:线程取消

1.8.1 取消一个线程

函数pthread_cancel()向由thread指定的线程发送一个取消请求。

#include <pthread.h>
int pthread_cancel(pthread_t thread);
//返回值:返回0表示成功,否则,返回一个正数

发出取消请求后,函数pthread_cancel()当即返回,不会等待目标线程退出。

1.8.2 取消状态及类型

函数pthread_setcancelstate()和pthread_setcanceltype()会设定标志,允许线程对取消请求的响应过程加以控制。

#include <pthread.h>
int pthread_setcancelstate(int state,int *oldstate);
int pthread_setcanceltype(int type,int *oldtype);
//返回值:返回0表示成功,否则,返回一个正数

函数pthread_setcancelstate()会将调用线程的取消状态设置为参数state所给定的值。该参数的值如下:

PTHREAD_CANCEL_DISABLE
线程不可取消。
PTHREAD_CANCEL_ENABLE
线程可以取消。
PTHREAD_CANCEL_ASYNCHRONOUS
可能会在任何时间点取消线程。
PTHREAD_CANCEL_DEFERED
取消请求保持挂起状态,直至达到取消点。

1.8.3 取消点

若将线程的取消性状态和类型分别置为启用和延迟,仅当线程抵达某个取消点(cancellation point)时,取消请求才会起作用。

1.8.4 线程可取消性的检测

函数pthread_testcancel()可以产生一个取消点。

#include <pthread.h>
void pthread_testcancel(void);

1.8.5 清理函数(cleanup handler)

函数pthread_cleanup_push()和pthread_cleanup_pop()分别负责调用线程清理函数添加和移除清理函数。

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

版权声明:本文为qq_34539334原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。