Linux高性能服务器开发-第三章

165 阅读7分钟

显示某个进程开启的所有线程: 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;
}