显示某个进程开启的所有线程: ps –Lf pid
创建线程比创建进程通常要快10倍甚至更多
线程之间的共享和非共享资源
内核空间 |
|---|
栈 |
共享库 |
堆 |
.bss |
.data |
.text(代码段) |
受保护的地址 |
共享资源
- 进程 ID 和父进程 ID
- 进程组 ID 和会话 ID
- 用户 ID 和 用户组 ID
- 文件描述符表
- 信号处置/处理
- 文件系统的相关信息:文件权限掩码 (umask)、当前工作目录
- 虚拟地址/用户 空间(除栈、.text段)
- 栈 .text段会分成一块一块, 每块由一个线程独占
- 初始化的全局变量在.data段
- 没有初始化的全局变量在.bss段
非共享资源
- 线程 ID
- 信号掩码
- 每个线程都有自己的阻塞信号集
- 线程特有数据
- 比如在栈中创建的数据
- error 变量
- 实时调度策略和优先级
- 栈,本地变量和函数的调用链接信息
pthread库
在Linux环境下线程的本 质仍是进程
NPTL,或称为 Native POSIX Thread Library,是Linux线程的一个新实现,它克服了LinuxThreads的缺点,同时也符合POSIX的需求。与 LinuxThreads相比,它在性能和稳定性方面都提供了重大的改进。 查看当前 pthread 库版本:
getconf GNU_LIBPTHREAD_VERSION
线程操作
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。子线程本质是主线程调用一个回调函数
线程是第三方库, 编译的时候链接动态库 -lpthread 或者 -pthread
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
- attr : 设置线程的属性,一般使用默认值,NULL
- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
- 线程本质是执行一个回调函数
- arg : 给第三个参数使用,一般用NULL
- 返回值:
- 成功:0
- 失败:返回错误号。这个错误号和之前errno不太一样。
- 获取错误号的信息: char * strerror(int errnum);
pthread_t pthread_self(void);
- 功能:获取当前的线程的线程ID
int pthread_equal(pthread_t t1, pthread_t t2);
- 功能:比较两个线程ID是否相等
- 不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的是使用结构体去实现的。
- 使用这个函数比较不会受平台影响
void pthread_exit(void *retval);
- 功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
- 参数:retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。
- 主线程也可以退出, 不会影响其他线程, 但是和进程退出exit(0) return 0不一样, 进程退出线程就都没了
- 注意不要返回局部变量
int pthread_join(pthread_t thread, void **retval);
- 功能:和一个已经终止的线程进行连接, 回收子线程的资源, 这个函数是阻塞函数,调用一次只能回收一个子线程, 一般在主线程中使用
- 参数:
- thread:需要回收的子线程的ID
- retval: 接收子线程退出时的返回值 因为子进程返回值是一个void *, 想要用传出参数接受就要用 void **
- 返回值:
- 0 : 成功
- 非0 : 失败,返回的错误号
int pthread_detach(pthread_t thread);
- 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
- 不能多次分离,会产生不可预料的行为。
- 不能去连接join()一个已经分离的线程,会报错。
- 参数:需要分离的线程的ID
- 返回值:
- 成功:0
- 失败:返回错误号
int pthread_cancel(pthread_t thread);
- 取消线程(让线程终止),但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止。
- 取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换,这个位置称之为取消点。
- 比如printf就是一个取消点, 要执行完系统调用之后才终止
线程属性
线程属性1
线程属性2
属性对象是不透明的,而且不能通过赋值直接进行修改。系统提供了一组函数,用于初始化、配置和销毁每种对象类型。初始化对象时,将为该对象分配内存。必须将此内存返回给系统(destory)。
在creat()的时候会设置所创建线程的属性attr
线程属性类型 pthread_attr_t
typedef struct{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void *stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
int pthread_attr_init(pthread_attr_t *attr); 成功0
int pthread_attr_destroy(pthread_attr_t *attr); 成功0
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); 成功0
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 成功0
线程是joinable或者是分离的detached。前者能够被其他线程收回其资源和杀死。
在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。
后者是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
- detachstate
- PTHREAD_CREATE_DETACHED
- PTHREAD_CREATE_JOINABLE(默认)
互斥
pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- mutex : 需要初始化的互斥量变量
- attr : 互斥量相关的属性,NULL
- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
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);
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 全局变量,所有的线程都共享这一份资源。
int tickets = 1000;
// 创建一个互斥量
pthread_mutex_t mutex;
void * sellticket(void * arg) {
// 卖票
while(1) {
// 加锁
pthread_mutex_lock(&mutex);
if(tickets > 0) {
usleep(6000);
printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
tickets--;
}else {
// 解锁
pthread_mutex_unlock(&mutex);
break;
}
// 解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
// 初始化互斥量
pthread_mutex_init(&mutex, NULL);
// 创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sellticket, NULL);
pthread_create(&tid2, NULL, sellticket, NULL);
pthread_create(&tid3, NULL, sellticket, NULL);
// 回收子线程的资源,阻塞
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
// 释放互斥量资源
pthread_mutex_destroy(&mutex);
return 0;
}
读写锁
读写锁的特点:
- 如果有线程读数据,则允许其它线程执行读操作,但不允许写操作。
- 如果有线程写数据,则其它线程都不允许读、写操作。
- 写的优先级高(同时强锁时)
- 最后一个读进程退出才真正解锁(引用计数)
pthread_rwlock_t
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
条件变量
条件变量不是锁, 配合互斥锁更好地实现线程同步, 防止盲目抢锁, 比如生产者消费者模型, 商品为空的时候, 不让消费者去抢锁
pthread_cond_t
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);
- mutex
- 当wait()阻塞的时候, 会对mutex解锁
- 当wait()通过的时候, 会对mutex加锁
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_signal(pthread_cond_t *cond);
- 每次唤醒的线程个数未知, 最少一个, 最多全部
int pthread_cond_broadcast(pthread_cond_t *cond);
- 唤醒全部
条件变量配合互斥锁实现生产者消费者模型
void * producer(void * arg) {
while(1) {
pthread_mutex_lock(&mutex);
pthread_cond_wait(&request, &mutex);
生产();
pthread_cond_signal(&reply);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void * customer(void * arg) {
while(1) {
上锁要在判断有无商品之前
pthread_mutex_lock(&mutex);
if(有商品()) {
消费();
pthread_mutex_unlock(&mutex);
} else {
pthread_cond_signal(&request);
pthread_cond_wait(&reply, &mutex);
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}
信号量
sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
- sem : 信号量变量的地址
- pshared : 0 用在线程间 ,非0 用在进程间
- value : 信号量中的值
int sem_wait(sem_t *sem);
- 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞
int sem_post(sem_t *sem);
- 对信号量解锁,调用一次对信号量的值+1
int sem_getvalue(sem_t *sem, int *sval);
int sem_destroy(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
信号量配合互斥锁实现生产者消费者问题
position 初始值为8
void * producer(void * arg) {
while(1) {
sem_wait(&position);
pthread_mutex_lock(&mutex);
生产();
pthread_mutex_unlock(&mutex);
sem_post(&product);
}
return NULL;
}
void * customer(void * arg) {
while(1) {
sem_wait(&product);
pthread_mutex_lock(&mutex);
消费();
pthread_mutex_unlock(&mutex);
sem_post(&position);
}
return NULL;
}