Linux线程详解

线程

对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。

1. 线程概念

  • LWP: light-weight Process,本质上仍然是进程(Linux环境下)
  • 线程与进程
    • 进程:不同的进程拥有独立的地址空间
    • 线程:也有PCB控制块,但是没有独立的地址空间,共享。
    • Linux下,线程是最小的执行单位,而进程是最小的资源分配单位。
  • 查看进程下的线程:ps -Lf 进程id
    • LWP是线程号,用于cpu分配时间的,而不是线程id号。
  • 栈帧空间:存放局部变量和临时值

2. 优缺点

  • 线程的共享资源:
    • 文件描述符
    • 每种信号的处理方式
    • 当前工作目录:由工作目录进程决定
    • 共享用户id和组id
    • 内存地址空间:(./text/.data/.bass/heap/动态库,即除了栈)。即也共享全局变量
  • 非共享
    • 线程id
    • 处理器现场和栈指针
    • 独立的栈空间
    • errno变量
    • 信号屏蔽字
    • 优先级调度
  • 优点
    • 提高程序的并发性
    • 开销小
    • 数据通信、共享数据方便
  • 缺点
    • 库函数,不稳定
    • 调试、编写困难、gdb调试支持
    • 对信号支持不好

线程同步

下面的函数都是成功返回0,失败返回错误号。

1. mutext

  • 初始化

        #include <pthread.h>
    
        int pthread_mutex_destroy(pthread_mutex_t *mutex);
        int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                               const pthread_mutexattr_t *restrict attr);
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    

    互斥量初始化后是1。

  • lock/unlock

        #include <pthread.h>
    
        int pthread_mutex_lock(pthread_mutex_t *mutex);     // 堵塞
        int pthread_mutex_trylock(pthread_mutex_t *mutex);  // 不堵塞
        int pthread_mutex_unlock(pthread_mutex_t *mutex);
    

    注意这是锁是为了保护共享数据区域,应该使得锁的粒度越小越好
    比如:

        while(1) {
            pthread_mutex_lock(&mutex);
            printf("h ");       // 1
            sleep(rand() % 3);
            printf("w.\n");     // 2
            pthread_mutex_unlock(&mutex); 
            sleep(rand() % 3);
        }
    

    上面的锁是为了保护stdout。unlock应该放在第二个print的位置,而不是放在最后一个sleep的后面。

  • 死锁

    • 同一个线程试图对同一个互斥量加锁两次,即两次调用pthread_mutex_lock,而中间没有调用pthread_mutex_unlock
    • 线程1拥有A锁,请求B锁,线程2拥有B锁,请求A锁
      • pthread_mutex_trylock:当拿不到锁时,就会放弃所有的锁,就不会有死锁。

2. 读写锁

  • 写独占,读共享,写锁优先级更高。相比较互斥量锁,效率更高,因为读时可以共享。
  • 函数
        int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                                const pthread_rwlockattr_t *restrict attr);
        
        int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
        int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    
        int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

3. 条件变量

  • 条件变量不是锁,但它可以造成线程堵塞。通常和mutex配合使用,给多线程提供一个会和的场所。

  • 函数

        #include <pthread.h>
    
       int pthread_cond_destroy(pthread_cond_t *cond);
       int pthread_cond_init(pthread_cond_t *restrict cond,
                             const pthread_condattr_t *restrict attr);
    
        int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                                   pthread_mutex_t *restrict mutex,
                                   const struct timespec *restrict abstime);
       int pthread_cond_wait(pthread_cond_t *restrict cond,
                             pthread_mutex_t *restrict mutex);
    
       int pthread_cond_broadcast(pthread_cond_t *cond);
       int pthread_cond_signal(pthread_cond_t *cond);
    
    • pthread_cond_wait:堵塞等待一个条件变量。
      • 堵塞等待条件变量cond满足
      • 释放已掌握的互斥锁,相当于pthread_mutex_unlock(&mutex)这两步是一个原子操作。
      • 当被唤醒,pthread_cond_wait函数返回时,解除堵塞并且重新申请获取互斥锁。
    • pthread_cond_broadcast/pthread_cond_signal:唤醒堵塞中的pthread_cond_wait
      • pthread_cond_broadcast:唤醒所有堵塞的条件量
      • pthread_cond_signal: 至少唤醒一个条件量
    • pthread_cond_timedwait:
      • 绝对时间的获取:
        time_t cur = time(NULL); // 获取当前的时间
        timespec t;
        t.tv_sec = cur + secons; // 在当前时间的基础上向后偏移才能获取绝对时间 
        
  • 生产者与消费者设计模式

  • 条件量的优点:
    相较于mutex可以减少竞争。

    如果直接使用mutex,除了生产者和消费者之间要竞争互斥量之外,消费者之间也需要竞争互斥量,但是如果链表中没有数据,消费者之间的竞争是没有意义的。通过条件变量的机制,只有在生产者完成生产之后,消费者之间才会开始竞争,提高了效率。

线程函数

只要使用了线程函数,编译时需要链接到-pthread

1. pthread_self

    #include <pthread.h>

    pthread_t pthread_self(void);

返回线程id。

2. pthread_Create

    #include <pthread.h>
 
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                        void *(*start_routine) (void *), void *arg);

创用于建线程,成功时候返回0,失败返回错误编号。

  • 参数
    • thread: 线程id
    • attr : 线程属性
    • start_routine: 线程函数,返回类型是void*,函数参数类型是void*
    • arg: 线程函数的参数
      • 注意这个参数传递是以值传递如果是以指针传递会有危险,因为以指针传递,有可能原本主线程的指针对象可能已经发生了改变

3. pthread_exit

    #include <pthread.h>

    void pthread_exit(void *retval);

退出的是单个线程,退出值存放在retval

  • exit:是直接将整个进程退出。无论exit在同一进程中的哪个位置只要调用了exit整个进程都会退出。

  • pthread_exit:只是将调用线程退出。只要在线程中调用了pthread_exit,即使是深层嵌套,那么该线程就会退出。

  • return:返回到函数调用者。只在mianreturn == exit

  • 任务:通过pthread_exit完成主线程等到所有的子线程完成。

        int main(int argc, char const *argv[]) {
            
            '''
    
            pthread_exit(NULL);
            return 0;
        }
    

    在主线程中调用pthread_exit,使得主线程退出,就不会执行后面的return,也就不会退出整个进程。因此仅当最后一个线程执行pthread_exit之后,整个进程才会退出。

4. pthread_join

    #include <pthread.h>

    int pthread_join(pthread_t thread, void **retval);

阻塞线程等待指定线程thread退出,获取退出状态,线程退出状态存储在retval。如果线程已经终止,函数立即返回。

  • 线程退出状态相关
    • 如果线程被取消了,即调用pthread_cancel,则退出状态是PTHREAD_CANCELED
    • pthread_exit(void* )的退出状态定义为类型void* ,而这里的是void**。即,原本pthread_exit是将值转换成void*pthread_join是为获取退出状态,需要用void**,即更深一级指针。
    • 在涉及到malloc/free时,也可以先开始在主线程中malloc,将所得地址指针再传入子线程,等一切结束在主线程中free

5. pthread_detach

    #include <pthread.h>

    int pthread_detach(pthread_t thread);

pthread_detach使得指定线程和主线程分离,并且不需要别的线程来对这个线程进程资源回收。当这个detached线程结束后,资源自动回收到系统

6.pthread_cancel

    #include <pthread.h>

    int pthread_cancel(pthread_t thread);

请求杀死指定线程。

目标线程的是否取消以及何时取消取决于目标线程的两个因素:cancelability state and type

  • cancelability state: 决定是否能取消。 这由函数pthread_setcancelstate(3)决定,默认情况是是能状态。
  • type:决定何时取消,默认type==defer,状态可以由函数pthread_setcanceltype(3)改变为asynchronous,即任意时候。
    • type==defer,线程只能在取消点到来时候取消。
      具体的取消点函数可以man 7 pthreads查看,一般都是系统调用函数。
    • type==dasynchronous,线程能立即取消。

7. 信号量

  • 函数
        #include <semaphore.h>
        int sem_init(sem_t *sem, int pshared, unsigned int value);
        int sem_destroy(sem_t *sem);
    
        int sem_wait(sem_t *sem);
        int sem_trywait(sem_t *sem);
        int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    
    • 编译时需要链接到:-pthread
    • 成功返回0,失败返回-1,设置errno
    • sem_t数据类型 : 本质上是结构体,但是在使用时可以将其看作是个整数,忽略实现细节。
    • sem_t sem  : 规定信号量大小不能小于0.
    • 既可以用于进程同步,也可以用于线程同步。
  • 信号基本操作
    • sem_wait:信号量大于0,则信号量–,类似于pthread_mutex_lock。信号量等于0时,造成线程阻塞。
    • sem_post:将信号量++,同时唤醒堵塞在信号量上的线程。类似于pthread_mutex_unlock
    • 信号量的初值,决定了占用信号量的线程个数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值