线程概念
- LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下)
- 进程:独立地址空间,拥有PCB
- 线程:也有PCB,但没有独立的地址空间(共享)
- 区别:在于是否共享地址空间。 独居(进程);合租(线程)。
- Linux下——
线程:最小的执行单位
进程:最小分配资源单位,可看成是只有一个线程的进程。
对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。
如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。
线程共享资源:
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
- 内存地址空间 (.text/.data/.bss/heap/共享库)
线程非共享资源:
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
- 调度优先级
线程优缺点:
优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好
线程函数
pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- 参数1:传出参数,保存系统为我们分配好的线程ID
- 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
- 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
- 参数4:线程主函数执行期间所使用的参数。
可以通过调用pthread_self(void)来获取当前线程ID
pthread_exit
void pthread_exit(void *retval);
参数retval传入要返回的数据,通常传NULL。
但要注意不能传在线程栈上分配的内存的数据
pthread_join
int pthread_join(pthread_t thread, void **retval); //成功0 失败 errno
回收线程
thread线程ID,retval存储线程结束状态
线程通过return或者pthread_exit返回的数据会通过retval接受到,如若对线程终止状态不感兴趣可以传递NULL给retval
pthread_detach
int pthread_detach(pthread_t thread); //成功 0 失败 errno
指定该状态,线程主动与主控线程断开关系,线程结束后自动释放。
调用该函数将不能通过pthread_join函数获取到线程退出状态,不然将返回EINVAL错误
pthread_cancel
int pthread_cancel(pthread_t thread); //成功 0 失败 errno
杀死线程,对应进程中的kill函数
该函数不是立即结束线程,而是需要线程达到某个取消点时才能结束
#include <stdio.h>
#include <pthread.h>
void* thread_func(void* ptr)
{
// 因为这个线程没有cancel point
while(1)
{
// 关闭cancel检测
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
sleep(10);
// 打开cancel检测
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 检查cancel point
pthread_testcancel();
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// 让线程退出
pthread_cancel(tid);
// 等待线程退出
pthread_join(tid, NULL);
}
被取消的线程,退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。因此当我们对一个已经被取消的线程使用pthread_join回收时,得到的返回值为-1。
pthread_equal
int pthread_equal(pthread_t t1, pthread_t t2);
比较两个线程ID是否相等
线程属性
对应pthread_create函数的const pthread_attr_t *attr参数
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
} pthread_attr_t;
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。
线程属性初始化
int pthread_attr_init(pthread_attr_t *attr); //成功 0 失败 errno //初始化
int pthread_attr_destroy(pthread_attr_t *attr); //成功 0 失败 errno //销毁
设置线程属性,分离or非分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- 获取程属性,分离or非分离
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数:
- attr:已初始化的线程属性
- detachstate:
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
其他属性使用并不常用,暂时还没学习
线程同步
pthread_mutex_init函数
pthread_mutex_destroy函数
pthread_mutex_lock函数
pthread_mutex_trylock函数
pthread_mutex_unlock函数
以上5个函数的返回值都是:成功返回0, 失败返回错误号。
pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。
pthread_mutex_t mutex; 变量mutex只有两种取值1、0。
pthread_mutex_init
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
初始化一个锁,参数一传入&mutex,参数二,假mutex为全局或者静态的并且用PTHREAD_MUTEX_INITIALIZER进行初始化可以不用init函数,而局部变量则应采用动态初始化,传入NULL
pthread_mutex_destory
int pthread_mutex_destroy(pthread_mutex_t *mutex); //销毁锁
pthread_mutex_lock
int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁
pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁
pthread_mutex_trylock
int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试加锁 加锁失败直接返回错误号,不阻塞
读写锁
pthread_rwlock_init函数
pthread_rwlock_destroy函数
pthread_rwlock_rdlock函数
pthread_rwlock_wrlock函数
pthread_rwlock_tryrdlock函数
pthread_rwlock_trywrlock函数
pthread_rwlock_unlock函数
以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_rwlock_t类型 用于定义一个读写锁变量。
pthread_rwlock_t rwlock;
pthread_rwlock_init
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //初始化读写锁 attr通常传NULL
pthread_rwlock_destroy
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); //销毁
pthread_rwlock_rdlock
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //以读方式请求读写锁
pthread_rwlock_wrlock
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //以写方式请求读写锁
pthread_rwlock_unlock
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); //解锁
pthread_rwlock_tryrdlock
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); //非阻塞读方式请求读写锁
pthread_rwlock_trywrlock
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //不解释
条件变量
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t类型 用于定义条件变量
pthread_cond_t cond;
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);
//阻塞等待条件变量cond满足并释放已掌握的互斥锁
//当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
//限时等待一个条件变量
//参看man sem_timedwait函数,查看struct timespec结构体。
int pthread_cond_signal(pthread_cond_t *cond); //唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒全部阻塞在条件变量上的线程
信号量
头文件 <semaphore.h>
sem_init函数
sem_destroy函数
sem_wait函数
sem_trywait函数
sem_timedwait函数
sem_post函数
以上6 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。(注意,它们没有pthread前缀)
sem_t类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。
sem_t sem; 规定信号量sem不能 < 0。
int sem_init(sem_t *sem, int pshared, unsigned int value);
//参1:sem信号量
//参2:pshared取0用于线程间;取非0(一般为1)用于进程间
//参3:value指定信号量初值
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
//参2:abs_timeout采用的是绝对时间。
↓
例如定时1秒:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
t.tv_nsec = t.tv_sec +100;
sem_timedwait(&sem, &t); 传参
互斥量
pthread_mutexattr_t mattr 类型: 用于定义mutex锁的【属性】
int pthread_mutexattr_init(pthread_mutexattr_t *attr); //初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); //销毁
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); //修改
参2:pshared取值: 线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有) 进程锁:PTHREAD_PROCESS_SHARED
文件锁
借助 fcntl函数来实现锁机制。 操作文件的进程没有获得锁时,可以打开,但无法执行read、write操作。
int fcntl(int fd, int cmd, ... /* arg */ );
参2:
F_SETLK (struct flock *) 设置文件锁(trylock)
F_SETLKW (struct flock *) 设置文件锁(lock)W –> wait
F_GETLK (struct flock *) 获取文件锁参3:
struct flock {
…
short l_type; 锁的类型:F_RDLCK 、F_WRLCK 、F_UNLCK
short l_whence; 偏移位置:SEEK_SET、SEEK_CUR、SEEK_END
off_t l_start; 起始偏移:1000
off_t l_len; 长度:0表示整个文件加锁
pid_t l_pid; 持有该锁的进程ID:(F_GETLK only)
…
};