Linux线程详解

简介: 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
  • 信号量的初值,决定了占用信号量的线程个数。
相关文章
|
5月前
|
消息中间件 存储 缓存
【嵌入式软件工程师面经】Linux系统编程(线程进程)
【嵌入式软件工程师面经】Linux系统编程(线程进程)
125 1
|
3月前
|
算法 Unix Linux
linux线程调度策略
linux线程调度策略
77 0
|
1月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
26 0
Linux C/C++之线程基础
|
1月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
3月前
|
缓存 Linux C语言
Linux线程是如何创建的
【8月更文挑战第5天】线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。
|
3月前
|
负载均衡 Linux 调度
在Linux中,进程和线程有何作用?
在Linux中,进程和线程有何作用?
|
3月前
|
缓存 Linux C语言
Linux中线程是如何创建的
【8月更文挑战第15天】线程并非纯内核机制,由内核态与用户态共同实现。
|
5月前
|
API
linux---线程互斥锁总结及代码实现
linux---线程互斥锁总结及代码实现
|
5月前
|
Linux API
Linux线程总结---线程的创建、退出、取消、回收、分离属性
Linux线程总结---线程的创建、退出、取消、回收、分离属性
|
5月前
|
API
Linux---线程读写锁详解及代码实现
Linux---线程读写锁详解及代码实现