• UNIX 线程相关知识体系


    线程标识

    每个线程有一个线程 ID,线程 ID 只有在它所属的进程上下文中才有意义。

    线程比较

    线程的 ID 是用 pthread_t 数据类型来表示的,实现的时候可以用一个结构来代表 pthread_t 数据类型,所以可移植的操作系统实现不能把它作为整数处理。因此需要如下函数对两个线程 ID 进行比较

    pthread_t 无符号长整形,此函数用来对两个线程 ID 进行比较
    若相等,返回非 0 数值;否则,返回 0
    extern int pthread_equal (pthread_t __thread1, pthread_t __thread2);
    
    • 1
    • 2
    • 3

    用结构表示 pthread_t 数据类型的后果是不能用一种可移植的方式打印该数据类型的值。

    线程 ID 获取

    线程可以通过 pthread_self 函数获得自身的线程 ID

    此函数可获得自身的线程 ID
    调用线程的线程 ID
    extern pthread_t pthread_self (void);
    
    • 1
    • 2
    • 3

    当线程需要识别以线程 ID 作为标识的数据结构时,pthread_self 函数可以与 pthread_equal 函数一起使用。

    线程创建

    线程创建
    创建线程,新创建的线程 ID 会被设置成
    若成功,返回 0;否则,返回错误编号 tidp 指向的内存单元
    extern int pthread_create (pthread_t *__restrict tidp,
                                   const pthread_attr_t *__restrict __attr,
                                   void *(*__start_routine) (void *),
                                   void *__restrict __arg);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当 pthread_creagte 成功返回时,新创建线程的线程 ID 会被设置成 tidp 指向的内存单元。attr 参数用于定制各种不同的线程属性,NULL 表示默认属性。
    新创建的线程从 start_rtn 函数的地址开始运行,该函数只有一个无类型指针参数 arg。如果需要向 start_rtn 函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为 arg 参数传入。
    创建线程时并不能保证哪个线程会先运行:是新创建的线程,还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。

    线程终止

    单个线程可以通过3种方式退出

    1. 线程可以简单地从启动例程中返回,返回值是线程的退出码;
    2. 线程可以被同一进程中的其他线程取消;
    3. 线程调用 pthread_exit;
    线程退出,rval_ptr是一个无类型的指针
    extern void pthread_exit(void *rval_ptr);
    
    • 1
    • 2

    rval_ptr 参数是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程也可以通过 pthread_join 函数访问到这个指针

    进程中的其他线程也可以通过调用 pthread_join 函数访问 rval_ptr 这个指针
    若成功,返回 0 ;否则,返回错误编号
    extern int pthread_join (pthread_t __th, void **__thread_return);
    
    • 1
    • 2
    • 3

    调用线程将一直阻塞,直到指定的线程调用 pthread_exit 、从启动例程中返回或者被取消。
    pthread_join 自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join 调用就会失败,返回 EINVAL。

    pthread_create 和 pthread_exit 函数的无类型指针参数可以传递的值不止一个,这个指针可以传递包含复杂信息的结构的地址,但是注意,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的。例如,在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内容可能已经改变了。又如,线程在自己的栈上分配了一个结构,然后把指向这个结构的指针传递给 pthread_exit ,那么调用 pthread_join 的线程试图使用该结构时,这个栈可能已经撤销,这块内存也另做他用。

    线程取消

    线程可以通过调用 pthread_cancel 函数来取消同一进程中的其他线程

    extern int pthread_cancel (pthread_t __th);
    
    • 1

    默认情况下,pthread_cancel 函数会使得由 tid 标识的线程的行为标识为如同调用了参数为 PTHREAD_CREATELED 的 pthread_exit 函数,但是,线程可以选择忽略取消或者控制如何被取消。
    注意 pthread_cancel 并不等待线程终止,它仅仅提出请求。

    线程分离

    在默认情况下,线程的终止状态会保存直到对线程调用 pthread_join。如果线程已经被分离,线程的底层存储资源可以在线程终止时立即被回收。在线程分离后,我们不能用 pthread_join 函数等待它的终止状态,因为对分离状态的线程调用 pthread_join 会产生未定义行为。可以调用 pthread_detach 分离线程。

    extern int pthread_detach (pthread_t __th)
    
    • 1

    线程同步

    当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种不一致就会出现。
    两个或多个线程视图在同一时间修改同一变量时,也需要进行同步。考虑变量增量操作的情况,增量操作通常分解为以下3步:

    1. 从内存单元读入寄存器;
    2. 把寄存器中对变量做增量操作;
    3. 把新的值写回内存单元;
    互斥量

    可以使用 pthread 的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。

    在使用互斥变量以前,必须首先对它进行初始化,可以把它设置为常量 PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以调用如下函数初始化

    初始化 pthread_mutex_t
    extern int pthread_mutex_init (pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr);
    销毁 pthread_mutex_t
    extern int pthread_mutex_destroy(pthread_mutex_t *__mutex);
    
    • 1
    • 2
    • 3
    • 4

    要使用默认的属性初始化互斥量,只需把 attr 设置为 NULL

    对互斥量进行加锁
    extern int pthread_mutex_lock (pthread_mutex_t *__mutex);
    释放
    extern int pthread_mutex_unlock (pthread_mutex_t *__mutex);
    
    • 1
    • 2
    • 3
    • 4

    如果不希望被阻塞,可以使用以下函数尝试对互斥量进行加锁。如果处于未锁住状态,那么将锁住互斥量,不会出现阻塞直接返回0,否则就会失败,不能锁住互斥量。

    尝试对互斥量进行加锁
    extern int pthread_mutex_trylock (pthread_mutex_t *__mutex);
    
    • 1
    • 2
    阻塞时间

    当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock 互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock 函数与 pthread_mutex_lock 是基本等价的,但是在达到超时时间值时, pthread_mutex_timedlock 不会对互斥量进行加锁,而是返回错误码。

    指定在时间 x 之前可以阻塞等待,
    extern int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
                                            const struct timespec *__restrict
                                            __abstime);
    
    • 1
    • 2
    • 3
    • 4

    读写锁

    互斥锁要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。
    读写锁可以有3种状态,读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁

    读写锁
    extern int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,
                                        const pthread_rwlockattr_t *__restrict
                                        __attr);
    
    • 1
    • 2
    • 3
    extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock);
    
    • 1
    • 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。
    • 当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。

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

    读写模式分不同的锁
    读模式下锁定读写锁
    extern int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock);
    写模式下锁定读写锁
    extern int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock);
    extern int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    读写锁条件版本
    读写锁条件版本
    extern int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);
    extern int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);
    
    • 1
    • 2
    • 3
    带有超时的读写锁
    带有超时的读写锁
    extern int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock,
                                               const struct timespec *__restrict __abstime);
    extern int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock,
                                               const struct timespec *__restrict __abstime);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    条件变量

    条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

    初始化
    条件变量,允许线程以无竞争的方式等待特定的条件发生
    条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件
    初始化
    extern int pthread_cond_init (pthread_cond_t *__restrict __cond,
                                      const pthread_condattr_t *__restrict __cond_attr);
    extern int pthread_cond_destroy (pthread_cond_t *__cond);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    传递给 pthread_cond_wait 的互斥量对条件进行保护。调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。
    而 pthread_cond_timedwait 带有超时时间。超时值指定了我们愿意等待多长时间,这个时间值是一个绝对数而不是相对数。可以使用 clock_gettime 函数获取 timespec 结构表示的当前时间。

    等待
    extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
                                      pthread_mutex_t *__restrict __mutex);
    extern int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
                                           pthread_mutex_t *__restrict __mutex,
                                           const struct timespec *__restrict __abstime);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    有两个函数可以用于通知线程条件已经满足。pthread_cond_signal 函数至少能唤醒一个等待条件的线程,而 pthread_cond_broadcast 函数则能唤醒等待该条件的所有线程。

        extern int pthread_cond_signal (pthread_cond_t *__cond);
        extern int pthread_cond_broadcast (pthread_cond_t *__cond);
    
    • 1
    • 2

    结合使用条件变量和互斥变量对线程进行同步的主要示例

    struct msg {
        struct msg *m_next;
    };
    
    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);
                mp = workq;
                workq = mp->m_next;
                pthread_mutex_unlock(&qlock);
                // todo
            }
        }
    }
    
    void enqueue_msg(struct msg *mp) {
        pthread_mutex_lock(&qlock);
        mp->m_next = workq;
        workq = mp;
        pthread_mutex_unlock(&qlock);
        pthread_cond_signal(&qready);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    我们用互斥量保护条件,在 while 循环中判断条件。把消息放到工作队列时,需要占有互斥量,但在给等待线程发信号时,不需要占有互斥量。只有线程在调用 pthread_cond_signal 之前把消息从队列中拖出了,就可以在释放互斥量以后完成这部分工作。

    自旋锁

    自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。
    自旋锁可用于以下情况:锁被持有的时间短,而线程不希望在重新调度上花费太多的成本。

    初始化

    自选锁
    extern int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared);
    extern int pthread_spin_destroy (pthread_spinlock_t *__lock);
    
    • 1
    • 2
    • 3

    使用 pthread_spin_lock 或 pthread_spin_trylock 对自旋锁进行加锁,前者在获取锁之前一直自旋,后者如果不能获取锁,就立即返回错误。

    extern int pthread_spin_lock (pthread_spinlock_t *__lock);
    extern int pthread_spin_trylock (pthread_spinlock_t *__lock);
    extern int pthread_spin_unlock (pthread_spinlock_t *__lock);
    
    • 1
    • 2
    • 3

    不管是 pthread_spin_lock 还是 pthread_spin_trylock,返回值为 0 的话就表示自旋锁被加锁。需要注意,不要调用在持有自旋锁情况下可能会进入休眠模式的函数。如果调用了这些函数,会浪费 cpu 资源,因为其他线程需要获取自旋锁需要等待的时间就延长了。

    屏障

    屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。
    pthread_join 函数就是一种屏障,允许一个线程等待,直到另一个线程退出。

    初始化

    初始化屏障时,可以使用 count 参数指定,在允许所有线程继续运行之前,必须达到屏障的线程数目。

    extern int pthread_barrier_init (pthread_barrier_t *__restrict __barrier,
                                         const pthread_barrierattr_t *__restrict
                                         __attr, unsigned int __count);
    extern int pthread_barrier_destroy (pthread_barrier_t *__barrier);
    
    • 1
    • 2
    • 3
    • 4

    使用下列函数来表明,线程已完成工作,准备等所有其他线程赶上来

    extern int pthread_barrier_wait (pthread_barrier_t *__barrier);
    
    • 1
  • 相关阅读:
    动手学习深度学习 06:卷积神经网络
    聚合物纳米材料造影剂供应|超声联合载血卟啉PLGA造影剂|携抗HER-2抗体PLGA高分子纳米超声造影剂
    关联规则之 Apriori 算法实现
    c语言的数据结构:队列
    poium测试库之JavaScript API封装原理
    编写一个程序,统计并输出“要把新兴领域改革作为进一步全面深化改革的一个重点突出出来,构建自主自强、开放融合、充满活力的创新生态,更好推进新兴领域战略能力建设。”中的汉字和标点符号的个数
    大一新生HTML期末作业——大学生抗疫感动专题网页设计作业
    android 垂直方向进度条progressbar
    zxing二维码位矩阵转换成Bitmap位图
    红眼睛微型成像仪拍照、存储与参数复位
  • 原文地址:https://blog.csdn.net/MldXTieJiang/article/details/127731916