zoukankan      html  css  js  c++  java
  • UNIX环境高级编程(11-线程)

    本章主要介绍线程的概念,创建和终止线程以及线程同步问题。

    使用到的函数默认需要包含pthread.h头文件,且在使用gcc编译时,需要链接pthread库。

    代码地址:https://gitee.com/maxiaowei/Linux/tree/master/apue

    线程的创建与终止

    创建线程

    // Returns: 0 if OK, error number on failure
    int pthread_create(pthread_t *restrict tidp,
                       const pthread_attr_t *restrict attr,
                       void *(*start_rtn)(void *), void *restrict arg);
    

    新创建的线程的线程ID被设置成tidp指向的内存单元;attr参数定制线程的不同属性;start_rtn函数是线程开始时执行的函数,其参数可以通过arg进行传递。

    注意:

    新线程最好不要通过tidp指向的内存空间获取自己的线程ID,因为如果新线程在主线程调用pthread_create返回前就运行了,那么它看到的就是未经初始化的内容,很可能并不是正确的线程ID。可以使用pthread_self函数获取自己的线程ID。

    pthread_t pthread_self(void);
    

    终止线程

    任意线程调用exit_Exit_exit会导致整个进程终止,可以通过以下3种方式,在不终止进程的前提下终止单个线程:

    1. 直接从启动实例中返回
    2. 被同一进程的其他线程取消
    3. 调用pthread_exit
    void pthread_exit(void *rval_ptr);
    
    // Returns: 0 if OK, error number on failure
    int pthread_join(pthread_t thread, void **rval_ptr);
    

    调用pthread_join的线程会一直阻塞,直到指定的线程终止。如果指定的线程直接返回或者是调用pthread_exit终止,则可以通过rval_ptr查看其返回值;如果线程是被取消的,则rval_ptr被设置为PTHRERAD_CANCELED

    取消线程

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

    用来请求取消同一进程中的其他线程。被取消的线程的行为表现为如同调用了参数为PTHRERAD_CANCELEDpthread_exit函数。但是,线程可以选择忽略或者控制如何被取消。

    线程清理处理程序

    void pthread_cleanup_push(void (*rtn)(void *), void *arg);
    void pthread_cleanup_pop(int execute);
    

    清理函数rtn只有在以下情况会执行:

    1. 调用pthread_exit
    2. 响应取消请求
    3. 非零execute参数调用pthread_cleanup_pop(为0时,清理函数不会被调用)

    这两个函数需要成对使用

    分离线程

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

    默认情况下,线程的终止状态会保留,直到调用pthread_join。如果线程被分离,则资源会在线程终止后被立即收回。

    线程同步

    互斥量mutex

    // All return: 0 if OK, error number on failure
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                         const pthread_mutexattr_t *restrict attr);
    int pthread_mutex_destroy(pthread_mutex_t *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);
    

    互斥变量为pthread_mutex_t类型,如果使用静态分配方式,可以直接使用PTHREAD_MUTEX_INITIALIZER进行初始化。对于动态分配的互斥量,在释放内存前需要调用pthread_mutex_destroy

    带有超时的互斥锁

    如果不希望线程在访问加锁的互斥量时无限等待,可以通过pthread_mutex_timedlock指定等待的绝对时间。

    #include <time.h>
    // Returns: 0 if OK, error number on failure
    int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
                              const struct timespec *restrict tsptr);
    

    示例

    #include <pthread.h>
    #include <time.h>
    
    #include "apue.h"
    
    int main()
    {
      int err;
      struct timespec tout;
      struct tm *tmp;
      char buf[64];
      pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    
    // 加锁
      pthread_mutex_lock(&lock);
      printf("mutex is locked.
    ");
      clock_gettime(CLOCK_REALTIME, &tout);
      tmp = localtime(&tout.tv_sec);
      strftime(buf, sizeof(buf), "%r", tmp);
      printf("current time is %s
    ", buf);
    
    // 设置超时
      tout.tv_sec += 10;
      err = pthread_mutex_timedlock(&lock, &tout);
      clock_gettime(CLOCK_REALTIME, &tout);
      tmp = localtime(&tout.tv_sec);
      strftime(buf, sizeof(buf), "%r", tmp);
      printf("the time is now %s
    ", buf);
    
      if(err == 0) {
        printf("mutex locked.
    ");
      } else {
        printf("can't lock mutex:%s
    ",strerror(err));
      }
    
      return 0;
    }
    

    读写锁rwlock

    读写锁有3中状态:不加锁、读模式加锁和写模式加锁。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

    读写锁适合对数据结构读的次数远大于写的情况。

    // Both return: 0 if OK, error number on failure
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                          const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

    读写锁在使用前必须初始化,在释放它们底层的内存前必须销毁

    // All return: 0 if OK, error number on failure
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 读模式锁定
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 写模式锁定
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    
    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
    int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
                                 const struct timespec *restrict tsptr);
    int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
                                 const struct timespec *restrict tsptr);
    

    与互斥量类似。

    条件变量cond

    当线程等待的条件变量被满足后,该线程就会被唤醒。条件变量需要和互斥量配合使用,条件本身是由互斥量保护的。

    在使用条件变量之前,必须对其进行初始化(有静态和动态2种方式)。

    // All return: 0 if OK, error number on failure
    int pthread_cond_init(pthread_cond_t *restrict cond,
                        const pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);
    
    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 tsptr);
    
    int pthread_cond_signal(pthread_cond_t *cond);    // 至少唤醒一个
    int pthread_cond_broadcast(pthread_cond_t *cond); // 全部唤醒
    

    pthread_cond_wait操作主要执行如下操作步骤:

    1.解锁互斥量mutex
    2.阻塞调用线程,直至另一线程就条件变量cond发出信号
    3.重新锁定mutex

    因此,在使用pthread_cond_wait函数之前,应该已经取得mutex锁。

    另外,对pthread_cond_wait的调用应该放在while循环中,因为从wait

    函数返回时,并不能确定条件已经得到满足(其他线程先醒来、虚假唤醒等),需要重新对条件进行判断。

    示例

    仅摘录主要代码,完整代码见ch11/pthread_cond.c

    // 消费者进程
    void *process_msg(void *arg)
    {
      for (;;) {
        pthread_mutex_lock(&qlock);
        while (count <= 0) {
          printf("%s wait msg
    ", tag);
          pthread_cond_wait(&qready, &qlock);
        }
        count--;
        pthread_mutex_unlock(&qlock);
        /* 处理消息 */
        // 放弃cpu,让另一个处理进场有机会得到数据
        sleep(1);
      }
      return NULL;
    }
    // 生产者进程
    int main(void)
    {
      for (;;) {
        pthread_mutex_lock(&qlock);
        count += 4;
        pthread_mutex_unlock(&qlock);
        // 测试两种唤醒方式
    #if 1
        pthread_cond_broadcast(&qready);
    #else
        pthread_cond_signal(&qready);
    #endif
        // 保证两个消费者进程都可以有时间处理数据
        sleep(3);
      }
      return 0;
    }
    

    自旋锁spin

    自旋锁与互斥量大体类似,主要的不同之处在于自旋锁在获取锁之前会一直忙等。因此,使用自旋锁应该保证持有锁的时间很短。

    自旋锁和互斥量的接口类似:

    // All return: 0 if OK, error number on failure
    int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
    int pthread_spin_destroy(pthread_spinlock_t *lock);
    
    int pthread_spin_lock(pthread_spinlock_t *lock);
    int pthread_spin_trylock(pthread_spinlock_t *lock);
    int pthread_spin_unlock(pthread_spinlock_t *lock);
    

    pshared表示进程共享(process-shared)属性,表明自旋锁的获取方式。它仅在支持线程进程共享同步(Thread Process-Shared Synchronization)的平台上有效,当设置为PTHREAD_PROCESS_SHARED,则只要线程可以访问锁底层内存,即使是不同进程的线程都可以获得锁;而设置为PTHREAD_PROCESS_PRIVATE后,只有初始化该锁的进程内部的线程可以访问它。

    屏障barrier

    屏障允许多个线程等待,直到所有合作线程满足某个点后,从该点继续执行。主线程可以将某个任务分解多个小任务交给不同的线程,等到所有线程工作完成后,主线程在此基础上继续执行。

    如书中的例子,使用8个线程分解800万个数的排序工作,每个线程对其中的100万个数排序,最后由主线程将这些结果进行合并。

    // Both return: 0 if OK, error number on failure
    int pthread_barrier_init(pthread_barrier_t *restrict barrier,
                           const pthread_barrierattr_t *restrict attr,
                           unsigned int count);
    int pthread_barrier_destroy(pthread_barrier_t *barrier);
    

    初始化函数中的count参数用于指定所有线程继续运行前,必须到达屏障的线程数。

    // Returns: 0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure
    int pthread_barrier_wait(pthread_barrier_t *barrier);
    

    wait函数表明当前线程已完成工作,准备等待其他线程。当线程调用该函数后满足屏障计数,那么函数的返回值为PTHREAD_BARRIER_SERIAL_THREAD,其余线程该函数返回值为0。这一特点使得可以很容易的将一个线程作为主线程,它可以工作在其他所有线程已完成的工作结果上。

    示例

    见ch11/pthread_barrier.c

    #include <pthread.h>
    
    #include "apue.h"
    
    pthread_barrier_t pb;
    pthread_t t1, t2;
    
    void *th1(void *a)
    {
      printf("start t1
    ");
      sleep(1);
      // 最后一个完成的线程,返回值应该为-1
      int r = pthread_barrier_wait(&pb);
      printf("th1  r:%d
    ", r);
      return NULL;
    }
    
    void *th2(void *a)
    {
      printf("start t2
    ");
      int r = pthread_barrier_wait(&pb);
      printf("th2  r:%d
    ", r);
      return NULL;
    }
    
    int main()
    {
      int r;
      pthread_barrier_init(&pb, NULL, 3);
    
      pthread_create(&t1, NULL, th1, NULL);
      pthread_create(&t2, NULL, th2, NULL);
    
      r = pthread_barrier_wait(&pb);
      printf("main r:%d
    ", r);
    
      // 等待子进程结束
      pthread_join(t1, NULL);
      pthread_join(t2, NULL);
      return 0;
    }
    
  • 相关阅读:
    c++ explicit 用法摘抄
    FBX SDK 从2012.1 到 2013.3 变化
    虚幻4 虚拟漫游场景 制作过程
    3DMAX 建立场景 工作流程
    保存路径选择对话框
    MFC 简单输出EXCEL
    快速使用Log4Cpp
    C# 调用 MFC DLL
    VS建立可供外部调用的MFC类DLL,C#调用MFC调用
    面试中被问到 “你对加班的看法” 该如何回答?
  • 原文地址:https://www.cnblogs.com/maxiaowei0216/p/14250250.html
Copyright © 2011-2022 走看看