1 线程基础
线程pi的类型为pthread_t
#include <pthread.h>
int pthread_equal( pthread_t tid1, pthread_t tid2 );
返回值:若相等则返回非0值,否则返回0
线程可以通过调用pthread_self函数获得自身的线程ID。为unsigned int
#include <pthread.h>
pthread_t pthread_self(void);
返回值:调用线程的线程ID
2 线程创建
- 在传统的UNIX进程模型中,每个进程只有一个控制线程。
- 在POSIX线程(pthread)的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的,在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。新增的线程可以通过调用pthread_create函数创建。
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);
返回值:若成功则返回0,否则返回错误编号
- tidp:当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建的线程的线程ID
- attr:参数用于定制各种不同的线程属性
- start_rtn:新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg
- arg: 当要传入参数不止一个时,需要将参数放入结构体
- 线程创建时并不能保证哪个线程会先运行
注意pthread函数在调用失败时通常会返回错误码,它们并不像其他的POSIX函数一样设置errno。
2.1 线程的属性之分离与结合(1)
线程属性
pthread_attr_init函数初始化pthread_attr_t结构,调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。
#include <pthread.h>
int pthread_attr_init( pthread_attr_t *attr );
int pthread_attr_destroy( pthtread_attr_t *attr );
两个函数的返回值都是:若成功则返回0,否则返回错误编号
如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy将会释放该内存空间。除此之外,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果该属性对象被误用,将会导致pthread_create函数返回错误。
名称 | 描述 |
---|---|
detachstate | 线程的分离状态属性 |
guardsize | 线程栈末尾的警戒缓冲区大小(字节数) |
stackaddr | 线程栈的最低地址 |
stacksize | 线程栈的大小(字节数) |
可以通过下列两个函数获取或者设置当前线程的熟悉,PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE,分离或者结合!
#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 );
两者的返回值都是:若成功则返回0,否则返回错误编号
#include "apue.h"
#include <pthread.h>
int
makethread(void *(*fn)(void *), void *arg)
{
int err;
pthread_t tid;
pthread_attr_t attr;
err = pthread_attr_init(&attr);
if(err != 0)
return(err);
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if(err == 0)
err = pthread_create(&tid, &attr, fn, arg);
pthread_attr_destroy(&attr);
return(err);
}
2.2 线程的属性之分离与结合(2)
在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。
-
joinable: 一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。(创建的线程默认式joinable,需要原有线程回收的)
-
detached相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
注意:a. 只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。 b. 而分离线程,自己运行结束后,线程立马终止,马上释放系统资源
当设置了一个分离线程后,这个线程运行非常快,但是可能在pthread_create函数返回之前就终止,它终止以后就可能讲线程号和系统资源移交给其他的线程使用,这样调用pthread_create会产生错误线程号,所以需要在分离线程中设置pthread_cond_timedwait,让它等一会,pthread_cond_timedwait(&cond,&mutex,&timeout)当timeout时间到,或者在时间内收到pthread_cond_signal时,会终止等待!
但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
将一个线程设置为detached 状态可以通过两种方式来实现。一种是调用 pthread_detach() 函数,可以将线程 th 设置为 detached 状态。另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态
#include <pthread.h>
void pthread_exit(void *retval);
void pthread_join(pthread_t th,void *thread_return);//挂起等待th结束,*thread_return=retval;
int pthread_detach(pthread_t th);
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);
3 线程的终止
如果进程中的任一线程调用了exit、_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。
单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。
-
线程只是从启动例程中返回,返回值是线程的退出码。
-
线程可以被同一进程中的其他线程取消。
-
线程调用pthread_exit。
#include <pthread.h>
void pthread_exit(void *rval_ptr);
rval_ptr指向的内存不能在栈上,否则join的时候指向的可能是随机值!
rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
返回值:若成功则返回0,否则返回错误编号
rval_ptr可以设置为NULL,如果对返回值不感兴趣!
#include "apue.h"
#include <pthread.h>
void *
thr_fn1(void *arg)
{
printf("thread 1 returning\n");
return((void *)1);
}
void *
thr_fn2(void *arg)
{
printf("thread 2 exiting\n");
pthread_exit((void *)2);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if(err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if(err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
err = pthread_join(tid1, &tret);
if(err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if(err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));
printf("thread 2 exit code %d\n", (int)tret);
exit(0);
}
下面一个例子是关于pthread_exit(栈对象),导致使用不正确
#include "apue.h"
#include <pthread.h>
struct foo {
int a, b, c, d;
};
void
printfoo(const char *s, const struct foo *fp)
{
printf(s);
printf(" structure at 0x%x\n", (unsigned)fp);
printf(" foo.a = %d\n", fp->a);
printf(" foo.b = %d\n", fp->b);
printf(" foo.c = %d\n", fp->c);
printf(" foo.d = %d\n", fp->d);
}
void *
thr_fn1(void *arg)
{
struct foo foo = {1, 2, 3, 4};
printfoo("thread 1:\n", &foo);
pthread_exit((void *)&foo);
printfoo("thread 1:\n", &foo);
}
void *
thr_fn2(void *arg)
{
printf("thread 2: ID is %d\n", pthread_self());
pthread_exit((void *)0);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
struct foo *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if(err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err = pthread_join(tid1, (void *)&fp); // 这里fp指针返回的值为pthread_exit的参数指针的指针,如果是栈可能是随机值
if(err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
sleep(1);
// printf("parent starting second thread\n");
// err = pthread_create(&tid2, NULL, thr_fn2, NULL);
// if(err != 0)
// err_quit("cant' create thread 2: %s\n", strerror(err));
sleep(1);
printfoo("parent: \n", fp);
exit(0);
}
线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程。
#include <pthread.h>
int pthread_cancel(pthread_t tid);
返回值:若成功则返回0,否则返回错误编号
在默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数
3.1 线程结束清理函数
线程清理处理程序(thread cleanup handler)与进程可以用atexit函数安排进程退出时需要调用的函数是类似的。但是该函数是存在栈上的,所以执行顺序与注册顺序相反!!!
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);//pthread_cleanup_pop将删除上次pthread_cleanup_push调用建立的清理处理程序。
上述两个函数实现方式为宏,必须成对使用!!!
pthread_cleanup_pop(1)为弹出执行函数,pthread_cleanup_popc(0)为不执行函数;
如果在之前线程pthread_exit或者响应cancel(当前线程默认PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DEFERRED),则不管pthread_cleanup_pop设置的值,均执行清理函数!
执行线程清理函数的条件如下:(注意线程返回的时候是不执行清理函数的)
- 调用pthread_exit时。
- 响应取消请求时。
- 用非零execute参数调用pthread_cleanup_pop时。(如果execute参数置为0,清理函数将不被调用。)
#include "apue.h"
#include <pthread.h>
void
cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void *
thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup, "thread 1 first hanlder");
pthread_cleanup_push(cleanup, "thread 1 second handler");
printf("thread 1 push complete\n");
if(arg)
return((void *)1); //线程返回,即线程结束,不会执行清理
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return((void *)1);
}
void *
thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, "thread 2 first handler");
pthread_cleanup_push(cleanup, "thread 2 second handler");
printf("thread 2 push complete\n");
if (arg)
pthread_exit((void *)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void *)2);
}
int
main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if(err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if(err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
err = pthread_join(tid1, &tret); //清理线程
if(err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if(err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));
printf("thread 2 exit code %d\n", (int)tret);
exit(0);
}