UNIX 线程相关知识体系

线程标识

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

线程比较

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

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

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

线程 ID 获取

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

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

当线程需要识别以线程 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);

当 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);

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

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

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

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

线程取消

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

extern int pthread_cancel (pthread_t __th);

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

线程分离

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

extern int pthread_detach (pthread_t __th)

线程同步

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

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

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

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

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

当线程试图获取一个已加锁的互斥量时,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);

读写锁

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

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

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

读写模式分不同的锁
读模式下锁定读写锁
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);
读写锁条件版本
读写锁条件版本
extern int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);
extern int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);
带有超时的读写锁
带有超时的读写锁
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);

条件变量

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

初始化
条件变量,允许线程以无竞争的方式等待特定的条件发生
条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件
初始化
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);

传递给 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);

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

    extern int pthread_cond_signal (pthread_cond_t *__cond);
    extern int pthread_cond_broadcast (pthread_cond_t *__cond);

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

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);
}

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

自旋锁

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

初始化

自选锁
extern int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared);
extern int pthread_spin_destroy (pthread_spinlock_t *__lock);

使用 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);

不管是 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);

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

extern int pthread_barrier_wait (pthread_barrier_t *__barrier);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值