APUE——线程创建与终止

线程链接
线程同步
线程同步1
线程的分离和结合
在这里插入图片描述

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 线程创建

  1. 在传统的UNIX进程模型中,每个进程只有一个控制线程。
  2. 在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,否则返回错误编号
  1. tidp:当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建的线程的线程ID
  2. attr:参数用于定制各种不同的线程属性
  3. start_rtn:新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg
  4. arg: 当要传入参数不止一个时,需要将参数放入结构体
  5. 线程创建时并不能保证哪个线程会先运行
    注意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)。

  1. joinable: 一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。(创建的线程默认式joinable,需要原有线程回收的)

  2. 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,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。
单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。

  1. 线程只是从启动例程中返回,返回值是线程的退出码。

  2. 线程可以被同一进程中的其他线程取消。

  3. 线程调用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设置的值,均执行清理函数
执行线程清理函数的条件如下:(注意线程返回的时候是不执行清理函数的)

  1. 调用pthread_exit时。
  2. 响应取消请求时。
  3. 用非零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);
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/104197695