线程
文章目录
1 概述
线程进程内独立的一条运行路线,处理器调度的小单元,也可以称为轻量级进程。线程可以对进程的内存空间和资 源进行访问,并与同一进程中的其他线程共享。
典型进程只有一个控制线程,有多个控制线程后,进程在某一时刻能做不止一件事。
为每种事件类型分配单独的处理线程,可简化处理异步事件的代码。
多个线程可自动访问相同的存储地址空间和文件描述符。一个进程的所有信息对进程的所有线程共享。
分解问题从而提高程序吞吐量,单线程进程完成多任务需把任务串行化,而多线程中相互独立的处理可以交叉进行。
使用多线程可改善响应时间。
注:多线程程序在单处理器上运行也能改善响应时间和吞吐量。
1.1 线程机制分类和特性
用户级线程:用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定。如果一个进程中的某一个线程调用了一个阻塞 的系统调用函数,那么该进程包括该进程中的其他所有线程也同时被阻塞。主要缺点是无法发挥多处理器的优势。
轻量级线程:内核支持的用户线程,是内核线程的一种抽象对象。
内核线程:允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。
1.2 线程标识
每个线程有一个线程ID,线程ID只有在它所属的进程上下文中才有意义,用pthread_t数据类型表示,不能把它作为整数处理,用函数对两线程ID进行比较。
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
//相等返回非0值,否则返回0
pthread_t pthread_self(void);
//返回调用线程的线程ID
2 线程编程
2.1 线程基本编程
pthread_create:创建线程,实际上就是确定调用该线程函数的入口点。
pthread_exit:主动退出线程。使用线程函数时, 不能随意使用 exit()退出函数进行出错处理,exit的作用是使调用进程终止,使用exit会使该进程中的所有线程都终止。
pthread_join:用于将当前线程挂起并等待线程结束,当函数返回时,被等待线程的资源就被收回。
pthread_cancel:在别的线程中终止另一个线程的执行,但在被取消的线程的 内部需要调用pthread_setcancel函数和 pthread_setcanceltype函数设置自己的取消状态。
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(start_rtn)(void *), void *restrict arg);
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr);
int pthread_cancel(pthread_t thread);
//成功返回0,出错返回错误码
tidp:线程标识符。
attr:线程属性设置,通常取为 NULL。
start_rtn:线程函数的起始地址,是一个以指向 void 的指针作为参数和返回值的函数指针。
arg:传递给start_rtn的参数 。
rval_ptr:线程结束时的返回值的指针,可由其他函数如pthread_join来获取。
thread:等待线程的标识符。
rval_ptr:用户定义的指针,用来存储被等待线程结束时的返回值。
3 线程间的同步与互斥
POSIX中两种线程同步机制:互斥锁更适合用于同时可用的资源是惟一的情况;信号量更适合用于同时可用的资源为多个的情况。
3.1 互斥锁线程控制
互斥锁:用一种简单的加锁方法来控制对共享资源的原子操作。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁 状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起, 直到上锁的线程释放掉互斥锁为止。
互斥锁初始化:pthread_mutex_init
互斥锁上锁:pthread_mutex_lock
互斥锁判断上锁:pthread_mutex_trylock
互斥锁接锁:pthread_mutex_unlock
消除互斥锁:pthread_mutex_destroy
互斥锁可分为3种:
快速锁:是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。
递归互斥锁:能够成功地返回,并且增加调用线程在互斥上加锁的次数,
检错互斥锁:快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//成功返回0,否则返回错误编号
mutex:互斥锁。
attr:
PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁。
死锁:若线程试图对同一个互斥量加锁两次,则它自身会陷入死锁状态。还有一种方式也能产生死锁:线程A占有互斥量A,并试图锁住互斥量B时阻塞,拥有互斥量B的线程B也在试图锁住互斥量A,两个线程都无法向前运行,也会产生死锁。
仔细控制互斥量加锁的顺序可避免死锁。如需要对互斥量A、B加锁,当所有进程都在加锁B之前先锁住A,则这两个互斥量不会产生死锁。或通过使用pthread_mutex_trylock接口避免死锁。
3.2 信号量线程控制
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。Linux 实现了 POSIX 的无名信号量,主要用于线程间的互斥与同步。
sem_init用于创建一个信号量,并初始化它的值。
sem_wait()和 sem_trywait()都相当于 P 操作,在信号量大于零时它们都能将信号量的值减一,两 者的区别在于若信号量小于零时,sem_wait()将会阻塞进程,而 sem_trywait()则会立即返回。
sem_post()相当于 V 操作,它将信号量的值加一同时发出信号来唤醒等待的进程。
sem_getvalue()用于得到信号量的值。
sem_destroy()用于删除信号量。
#include <semaphore.h>
#include <pthread.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem);
int sem_destroy(sem_t *sem);
//成功返回0,出错返回-1
sem:信号量指针。
pshared:决定信号量能否在几个进程间共享。只能取 0,就表示这个信号量是当前进程的局部信号量 。
value:信号量初始化值。
4 线程属性
4.1 概述
线程的多项属性都是可以更改的,主要包括绑定属性、分离属性、堆栈地址、 堆栈大小以及优先级。其中系统默认的属性为非绑定、非分离、缺省 1M 的堆栈以及与父进程同样级别的优先级。
绑定属性:指一个用户线程固定地分配给一个内核线程,非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。
分离属性:用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时,它所占 用的系统资源并没有被释放,也就是没有真正的终止。在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。
4.2 函数
pthread_attr_init:进行初始化。
pthread_attr_setscope:设置绑定属性。
pthread_attr_setdetachstate:设置线程分离属性的函数。
pthread_attr_getschedparam:获取线程优先级。
pthread_attr_setschedparam:设置线程优先级。
pthread_attr_destroy:函数对分配的属性结构指针进行清理和回收。
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param);
int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param);
//成功返回0,出错返回错误码
scope:PTHREAD_SCOPE_SYSTEM(绑定 ),PTHREAD_SCOPE_PROCESS(非绑定 )。
detachstate:PTHREAD_CREATE_DETACHED(分离),PTHREAD _CREATE_JOINABLE(非分离 )。
param:线程优先级。
5 生产者消费者实验
5.1 概述
“生产者消费者”问题是一个著名的同时性编程问题的集合。有一个有限缓冲区和两个线程:生产者和消费者。他们分别不停地把产品放入缓冲区和从缓冲区中拿走产 品。一个生产者在缓冲区满的时候必须等待,一个消费者在缓冲区空的时候也必须等待。另外缓冲区是临界资源,生产者和消费者之间必须互斥执行。
5.2 解决方案
*:线程优先级。
5 生产者消费者实验
5.1 概述
“生产者消费者”问题是一个著名的同时性编程问题的集合。有一个有限缓冲区和两个线程:生产者和消费者。他们分别不停地把产品放入缓冲区和从缓冲区中拿走产 品。一个生产者在缓冲区满的时候必须等待,一个消费者在缓冲区空的时候也必须等待。另外缓冲区是临界资源,生产者和消费者之间必须互斥执行。
5.2 解决方案
使用 3个信号量,其中两个信号量 avail 和full 分别用于解决生产者和消费者线程之间的同步问题,mutex 是 用于这两个线程之间的互斥问题。其中 avail 表示有界缓冲区中的空单元数,初始值为 N;full 表示有界缓冲区中 非空单元数,初始值为 0;mutex 是互斥信号量,初始值为 1。
来源:https://blog.csdn.net/qq_42097578/article/details/100133814