Unix高级环境编程系列笔记

  • 如何来标识一个线程?
  • 如何创建一个新线程?
  • 如何实现单个线程的退出?
  • 如何使调用线程阻塞等待指定线程的退出,并获得退出线程的返回码?
  • 如何通过一个线程让另外一个线程退出?
  • 如何实现线程退出时的清理动作?
  • Unix系统如何实现线程之间的同步?
  • 什么情况会发生线程死锁,如何避免死锁?
  • 读写锁的使用方法。
  • 什么是条件变量,它有什么作用?
  • 如何使用条件变量?

        1. 如何来标识一个线程?

        表示进程号的为pid_t类型,表示线程号的是pthread_t类型。pthread_t是一个结构体而不是整型。

        使用pthread_equal确定两个线程号是否相等:

    #include 
    int pthread_equal(pthread_t tid1, pthread_t tid2);
    Returns: nonzero if equal, 0 otherwise
    

        使用pthread_self函数来获取线程的ID:

    #include 
    pthread_t pthread_self(void);
    Returns: the thread ID of the calling thread
    

        2.如何创建一个新线程?

        使用pthread_create函数创建一个新线程。

    以下是代码片段:
    #include 
    int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);
    Returns: 0 if OK, error number on failure

        当该函数成功返回的时候,tidp所指向的内存位置将被分配给新创建的带有thread ID的线程。

        attr用来定制各种线程参数。

        新创建的线程将在start_rtn函数所指向的地址开始运行,该函数接受一个参数无类型的指针arg作为参数

        线程创建时无法保证哪个线程会先运行。新创建的线程可以访问进程地址空间,并且继承了调用线程的浮点环境以及信号量掩码,但对于线程的未决信号量也将会被清除。

        下面的这段程序创建新的线程,并打印线程id,新线程通过pthread_self函数获取自己的线程ID。

    #include "apue.h"
    #include 
    pthread_t ntid;
    void printids(const char *s)
    {
        pid_t      pid;
        pthread_t  tid;
        pid = getpid();
        tid = pthread_self();
        printf("%s pid %u tid %u (0x%x)\\n", s, (unsigned int)pid,
          (unsigned int)tid, (unsigned int)tid);
    }
    void * thr_fn(void *arg)
    {
        printids("new thread: ");
        return((void *)0);
    }
    int main(void)
    {
        int     err;
        err = pthread_create(&ntid, NULL, thr_fn, NULL);
        if (err != 0)
            err_quit("can\'t create thread: %s\\n", strerror(err));
        printids("main thread:");
        sleep(1);
        exit(0);
    }
    

        3. 如何实现单个线程的退出?

        如果一个线程调用了exit, _Exit, 或者_exit,将导致整个进程的终止。要实现单个线程的退出,可以采用如下方式:

        o 线程可以简单的从start routine返回,返回值就是线程的退出代码。

        o 线程可以被同一进程中的其它线程终止。

        o 线程调用pthread_exit

    #include 
    void pthread_exit(void *rval_ptr);
    

        4.如何使调用线程阻塞等待指定线程的退出,并获得退出线程的返回码?

    #include 
    int pthread_join(pthread_t thread, void **rval_ptr);
    Returns: 0 if OK, error number on failure
    

        调用线程将会被阻塞直到指定的线程终止。如果线程简单的从start routine返回则rval_ptr将包含返回代码。如果线程是被撤销(调用pthread_exit)的,rval_ptr指向的内存地址将被设置为PTHREAD_CANCELED.

        通过调用pthread_join,我们自动的将一个线程变成分离状态,这样就可以实现资源的回收。如果线程已经处于分离状态,调用pthread_join将会失败,并返回EINVAL。

        如果我们对于线程的返回值不感兴趣,可以将rval_ptr设置成NULL。

        一段有缺陷的代码:

    #include "apue.h"
    #include 
    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);
    }
    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);
        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("can\'t create thread 2: %s\\n", strerror(err));
        sleep(1);
        printfoo("parent:\\n", fp);
        exit(0);
    }
    

        注意,pthread_create 和 pthread_exit函数的无类型指针可以传递复杂的结构信息,但这个结构所使用的内存在调用者完成后必须仍然有效(分配在堆上或者是静态变量),否则就会出现使用无效的错误。这段代码中thr_fn1函数中变量foo分配在栈上,但该线程退出后,主线程通过pthread_join获取foo的地址并进行操作(调用printfoo函数时)就会出现错误,因为此时thr_fn1已经退出它的栈已经被销毁。

        5.如何通过一个线程让另外一个线程退出?

        调用pthread_cancel函数将导致tid所指向的线程终止运行。但是,一个线程可以选择忽略其它线程控制该线程何时退出。注意,该函数并不等待线程终止,它仅仅提出要求。

    #include 
    int pthread_cancel(pthread_t tid);
    Returns: 0 if OK, error number on failure
    

        6.如何实现线程退出时的清理动作?

        线程可以建立多个清理处理程序,这些程序记录在栈中,也就是说他们的执行顺序与注册顺序想法。使用如下函数注册清理函数:

        void pthread_cleanup_push(void (*rtn)(void *), void *arg);

        void pthread_cleanup_pop(int execute);

        rtn将被调用,并传以arg参数,引起该函数调用的情况如下:

        o 调用pthread_exit

        o 对于退出请求的反应

        o 以非0参数调用pthread_cleanup_push

        如果pthread_cleanup_pop的参数非0则仅仅移除该处理函数而不执行。

        如果函数已经处于分离状态,则当它退出时线程底层的存储资源会被立即回收。处于分离状态的线程,如果调用pthread_join来等待其退出将会出现错误。

        通过下列函数可以让进程处于分离状态:

    #include 
    int pthread_detach(pthread_t tid);
    Returns: 0 if OK, error number on failure
    

        7.Unix系统如何实现线程之间的同步?

        使用pthreads mutual-exclusion interfaces。引入了mutex,用pthread_mutex_t类型来表示。在使用这个变量之前,我们首先要将其初始化,或者赋值为PTHREAD_MUTEX_INITIALIZER(仅仅用于静态分配的mutexs),或者调用pthread_mutex_init。如果我们动态的为mutex分配空间(例如通过调用malloc),我们需要在调用free释放内存之前调用pthread_mutex_destroy。

        函数定义如下:

    以下是代码片段:
    #include 
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,  const pthread_mutexattr_t *restrict attr);
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    Both return: 0 if OK, error number on failure

        初始化mutex时参数attr用来指定mutex的属性,要使用默认值将它设置为NULL。

        使用如下函数对mutex进行加锁或解锁:

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    All return: 0 if OK, error number on failure
    

        注意当mutex已经被加锁则 pthread_mutex_lock会阻塞。如果一个线程无法忍受阻塞,可以调用pthread_mutex_trylock来加锁,加锁失败则立即返回EBUSY。

        8.什么情况会发生线程死锁,如何避免死锁?

        如果一个线程对mutex加两次锁则显然会导致死锁。但实际上死锁的情况要复杂的多:when we use more than one mutex in our programs, a deadlock can occur if we allow one thread to hold a mutex and block while trying to lock a second mutex at the same time that another thread holding the second mutex tries to lock the first mutex. Neither thread can proceed, because each needs a resource that is held by the other, so we have a deadlock.

        死锁可以通过控制加锁的顺序来避免。有两个mutex A和B,如果所有的线程总是先对A加锁再对B加锁就不会产生死锁。但实际应用中可能很难保证这种顺序加锁的方式,这种情况下,可以使用pthread_mutex_trylock来避免死锁的发生。

        9.读写锁的使用方法。

        读写锁的初始化与销毁:

    以下是代码片段:
    #include 
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    Both return: 0 if OK, error number on failure
        对于读写锁的初始化与销毁独占锁类似。

        加锁与解锁:

    #include 
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    All return: 0 if OK, error number on failure
    

        对于读者的数量会有限制,因此调用 pthread_rwlock_rdlock时需要检查返回值。

        在正确使用的情况下,不需要检查pthread_rwlock_wrlock和pthread_rwlock_unlock的返回值。

        条件加锁:

    #include 
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    Both return: 0 if OK, error number on failure
    

        10.什么是条件变量,它有什么作用?

        条件变量是线程可用的另外一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定条件的发生。条件本身是由互斥量保护的。线程在改变状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会觉察到这种变化。

        11.如何使用条件变量?

        条件变量的类型为pthread_cond_t ,其初始化与销毁的方式与mutex类似,注意静态变量可以通过指定常量PTHREAD_COND_INITIALIZER来进行初始化。

    #include 
    int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);
    Both return: 0 if OK, error number on failure
    

        使用pthread_cond_wait来等待条件变成真。

        函数定义如下:

    以下是代码片段:
    #include 
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    int pthread_cond_timedwait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

    Both return: 0 if OK, error number on failure
        调用者把锁住的mutex传递给pthread_cond_wait,函数把调用线程放到等待条件变量的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间窗口。pthread_cond_wait返回时,mutex会再次被锁住。

        注意,调用成功返回后,线程需要重新计算条件变量,因为其它线程可能已经改变了条件。

        有两个函数用于通知线程一个条件已经被满足。pthread_cond_signal函数用来唤醒一个等待条件满足的线程, pthread_cond_broadcast用来唤醒所有等待条件满足的线程。

        他们的定义为:

    #include 
    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond);
    Both return: 0 if OK, error number on failure
    

        下面的这段代码实现了类似于生产者消费者模型的程序,生产者通过enqueue_msg将消息放入队列,并发送信号通知给消费者线程。消费者线程被唤醒然后处理消息。

    #include 
    struct msg {
        struct msg *m_next;
        /* ... more stuff here ... */
    };
    struct msg *workq;
    pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
    void
    process_msg(void)
    {
        struct msg *mp;
        for (;;) {
            pthread_mutex_lock(&qlock);
            while (workq == NULL)
                pthread_cond_wait(&qready, &qlock);
     /*get msg from the queue*/
            mp = workq;
            workq = mp->m_next;
            pthread_mutex_unlock(&qlock);
            /* now process the message mp */
        }
    }
    void
    enqueue_msg(struct msg *mp)
    {
        pthread_mutex_lock(&qlock);
        /*put msg in queue*/
        mp->m_next = workq;
        workq = mp;
        pthread_mutex_unlock(&qlock);
        pthread_cond_signal(&qready);
    }
    

        在pthread_cond_signal发送消息之前并不需要占用锁,因为一旦线程被唤醒后通过while发现没有要处理的msg存在则会再次陷入睡眠。如果系统不能容忍这种竞争环境,则需要在unlock之前调用cond_signal,但是在多处理器机器上,这样会导致多线程被唤醒然后立即进入阻塞(cond_signal唤醒线程,但由于我们仍占用着锁,所以这些线程又会立即阻塞)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值