• 周六. 8月 20th, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Linux多线程开发I

admin

11月 28, 2021
1.线程是允许应用进程并发执行多个任务的一种机制。一个进程可以包含多个线程。
  • 同一个程序中多有的线程均会独立执行相同程序,且共享同一份全局内存区域,包括初始化数据段、未初始化数据段、堆内存段。
  • 进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。
  • 线程是轻量级的进程(LWP:Light Weight Process),在Linux环境下线程的本质仍然是进程。
2.查看指定进程的LWP号。
ps -Lf pid

 

3.线程和进程的区别。
  • 进程间的信息难以共享。父子进程之间并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
  • 调用fork()创建进程的时间开销较高,即使是写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性。
  • 线程之间能够方便快速地共享信息。只需要将数据复制到共享(全局/堆)变量中即可。
  • 创建线程比进程快10倍之多。线程间共享虚拟地址空间,无需采用写时复制来复制内存,也无需复制页表。
 
4.线程之间共享和非共享资源。
  • 共享资源
    • 进程ID和父进程ID
    • 进程组ID和会话ID
    • 用户ID和用户组ID
    • 文件描述符表
    • 信号处置
    • 文件系统相关的信息:文件权限掩码(umask)、当前工作目录
    • 虚拟地址空间(除栈、代码段.text)
  • 非共享资源
    • 线程ID
    • 信号掩码(阻塞信号集)
    • 线程特有数据
    • error变量
    • 实时调度策略和优先级
    • 栈,本地变量和函数的调用链接信息
 
5.线程相关函数。编译连接需要加上参数 -pthread
# 查看线程相关函数
$ man pthread  (tab键显示所有相关list)
 
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    - 作用:创建一个子线程。
            - 一般情况下,main函数所在的线程称之为主线程(main线程),其余创建的线程成为子线程。
            - 程序中默认只有一个进程,调用fork()函数后有2个进程
            - 程序默认只有一个线程,调用pthread_create()之后有2个线程
    - thread: 传出参数,线程创建成功后,子线程的线程ID被写道该变量中
    - attr: 设置线程的属性,一般使用默认值,NULL
    - start_rountine: 函数指针,这个函数是子线程需要处理的逻辑代码
    - arg: 给第三个参数使用,传参
    - 返回值:成功返回0,失败返回错误号,通过 char* strerror(int  errnum); 获取错误号信息
 
// 主线程退出时,不会影响其它正常运行的线程
void pthread_exit(void *retval);
    - 作用:终止线程,在哪个线程中调用,就表示终止哪个线程
    - retval: 传递一个指针,作为一个返回值,可以在pthread_join()中获得
 
pthread_t pthread_self(void)
    - 作用:获取当前线程的线程ID
 
int pthread_equal(pthread_t t1, pthread_t t2);
    - 作用:比较两个线程ID是否相等
 
// 任何线程都可以回收其他终止的线程的资源
int pthread_join(pthread_t thread, void **retval);
    - 作用:和一个已经终止的线程进行连接,回收子线程的资源
        - 这个函数是阻塞的,调用一次只能回收一个子线程,一般在主线程中使用    
    - thread: 需要回收的子线程的ID
    - retval: 接受子线程退出时的返回值
    - 返回值:成功返回0,失败返回错误号
 
int pthread_detach(pthread_t thread);
    - 作用:分离线程,被分离的线程在终止时,自动释放资源返回给系统
        - 不会阻塞父进程
        - 不能多次分离
        - 不能连接一个已经分离的线程
    - thread: 线程ID 
    - 返回值:成功返回0,失败返回错误号
 
int pthread_cancel(pthread_t thread);
    - 作用:取消线程(让线程终止)
        - 取消某个进程,可以终止某个线程的运行,但并不是马上终止,而是当子线程执行到一个取消点,线程才会终止
        - 取消点:系统规定好的一些系统调用,可以粗略的理解为用户区到内核区的切换,这个位置称之为取消点
    - thread: 线程ID
    - 返回值:成功返回0,失败返回错误号

  

6.线程属性。
int pthread_attr_init(pthread_attr_t *attr);
    - 作用:初始化线程属性变量
 
int pthread_attr_destory(pthread_attr_t *attr);
    - 作用:释放线程属性的资源
 
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
    - 作用:获取线程分离的状态属性
 
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    - 作用:设置线程分离的状态属性
    - attr: 线程的属性,可以通过pthread_attr_init获得
    - detachstate: 分离状态,PTHREAD_CREATE_DETACHED:使用attr创建的线程将以分离状态创建;PTHREAD_CREATE_JOINABLE:使用attr创建的线程将以可连接状态创建(默认)

  

7.线程同步。
  • 线程的优势是可以通过全局变量来共享信息。但是必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。
  • 临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,即同时访问统一资源的其他线程不应中断该片段的执行。
  • 线程同步:当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。
 
 
8.互斥量(互斥锁,mutex,mutual exclusion)用来确保同时仅有一个线程可以访问某项共享资源。
  • 两种状态:已锁定和未锁定。至多只有一个线程可以锁定该互斥量。
  • 一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。
  • 如果多个线程试图执行临界代码,其中一个线程抢到CPU资源,将锁定互斥量,然后访问临界代码,之后解锁互斥量,期间其他想要访问互斥量的线程将被阻塞。
 
9.互斥量相关操作函数。
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_destory(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);
     - 作用:解锁

  

10.死锁。两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,这些进程都无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
11.死锁产生的场景。
  • 忘记释放锁
  • 重复加锁
  • 多线程多锁,抢占锁资源
 
12.读写锁。允许多个读出,只允许一个写入。
  • 如果有其他线程读数据,允许其他线程执行读操作,但不允许写操作。
  • 如果有其他线程写数据,其他线程都不允许读、写操作。
  • 写是独占的,写的优先级高。
 
13.读写锁相关操作函数。
int pthread_rwlock_t    读写锁类型
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restict attr);
 
int pthread_rwlock_destory(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);

  

14.生产者和消费者模型。
 
15.条件变量。用于阻塞线程。
  • 条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用来实现线程同步。
  • 条件变量使线程睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
    • 一个线程阻塞等待”条件变量的条件成立”;  pthread_cond_wait
    • 另一个线程使”条件成立”(给出条件成立信号);pthread_cond_signal
  • 条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
pthread_cond_t        条件变量类型
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr *restrict attr);
 
int pthread_cond_destory(pthread_cond_t * cond);
 
int pthread_cond_wait(pthread_cond_t *restrict cond, const pthread_mutex_t *restrict mutex);
    - 作用:阻塞函数等待条件变量,调用了该函数,线程会阻塞
 
# 当这个函数调用阻塞的时候,会对互斥锁mutex进行解锁,当不阻塞时,继续向下执行前会对对互斥锁重新加锁
int pthread_cond_timedwait(pthread_cond_t *restrict cond, const pthread_mutex_t *restrict mutex, const struct *restrict abstime);
    - 作用:等待多长时间,调用该函数,线程会阻塞,直到指定的时间结束
 
int pthread_cond_signal(pthread_cond_t * cond);
    - 作用:唤醒1个或多个线程
 
int pthread_cond_broadcast(pthread_cond_t * cond);
    - 作用:唤醒全部线程

  

16.信号量。用于阻塞线程。
sem_t            信号量的类型
int sem_init(sem_t *sem, int pshared, unsigned int value);
    - 作用:初始化信号量
    - sem: 信号量变量的地址
    - pshared: 0 用在线程间,非0 用在进程间
    - value: 信号量的值
 
int sem_destory(sem_t *sem);
    - 作用:销毁信号量
 
int sem_wait(sem_t *sem);
    - 作用:对信号量加锁,如果信号量的值大于0,则对信号量的值-1并立即返回;如果为0就阻塞,直到信号量的值可以减少或其他信号处理器终止了该函数的调用
    - 返回值:成功返回0,失败返回-1并设置errno    
 
int sem_trywait(sem_t *sem);
 
int sem_timedwait(sem_t *sem, const struct timespec * abs_timeout);
 
int sem_post(sem_t *sem);
    - 作用:对信号量解锁,对信号量的值+1;如果信号量的值因此大于零,那么在sem_wait调用中被阻塞的另一个线程将被唤醒,并继续锁定信号量
    - 返回值:成功返回0,失败返回-1并设置errno
 
int sem_getvalue(sem_t *sem, int *sval);
 

  

 
 
 
 
 
 

发表回复

您的电子邮箱地址不会被公开。