学习脉络
序
一个进程内部的控制序列,被称之为“线程”(一切进程至少都存在一个执行线程),线程在进程内部的运行,本质是在进程地址空间内运行。
一:线程
- 哲学家进餐问题
这也是我们学习线程时,一个较为知名的例子,就是哲学家进餐和公交售票,生产消费模型了,所以可以通过这样的一个例子来更好的加深对于线程的了解。 - 线程
进程若是创建100个的话,则pcb和mmap都增加100个;若线程增加100个的话,pcb增加100个,但内存空间mmap不增加,两者的vm里都会增加线程栈。 - 线程创建函数
线程创建若返回零则表示成功,否则返回的就直接是它的错误号。 - 线程id
self所能够获取到的线程id是线程栈的首地址(进行进程范围内的比较)
pid_t pid
线程idpid_t tgid
进程组id
- 进程和线程函数的比较
-
能够杀死进程的kill却无法杀死线程,因为kill函数所传递的参数会先被线程进行接收,如果县城无法接收的话则会被它所再得进程给接收,是无法直接将线程杀死的;而对于线程得cancel函数,不是直接取消掉,而是达到一个点才会进行取消。
-
使用
join
的话,可以回收当前它所申请的空间, 或者是如果再创建线程的话会覆盖掉之前结束掉的线程所开的空间,而如果不加join的话,则会不断的去申请新的线程栈空间,造成了资源的泄露。 -
如果用
exit
来结束线程的话,main函数是不是也会结束呢?
这样的情况下,主函数退出了,但线程却没有退出而成为了僵尸状态;而多线程的情况下,僵尸是可以被杀死的。
到底能够开多少线程,受到这三个因素的影响:
二:互斥量
1. 互斥量函数
互斥量是用来上锁的,也就是说避免在一个线程运行的途中被其他线程打断,造成不必要的影响。锁是有代价的,但是对于一些共享变量来说,不上锁却是不行的,因此上锁的时候我们尽可能地锁那些最小的那部分互斥的关系,也应该考虑到整体的运行情况。
2. 死锁和活锁
-
死锁:多个线程之间循环申请调用,因此造成死锁的现象存在。
-
活锁:是用
trylock
会导致活锁,是因为trylock能够拿到就锁,拿不到就会把之前的释放,因此导致活锁,也就是两个路人撞见后,同时向同一侧让路,仍然会导致相撞。 -
死锁和活锁都不会发生崩溃。
3. 任意时间段把锁中间的线程给删掉 ——cancel
其触发的条件:
pthread_exit
pthread_cancel
cleanup_pop(1)
从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。
4. 读写锁
为了避免读过程中加锁造成查找的操作,降低程序的效率,我们可以使用读写锁。
都是读的操作,则进行共享,若是读和写两个操作则互斥
写独占,读共享,写锁优先级高!
- 若当前无锁状态,则读锁请求和写锁请求均可以进来
- 若当前读锁状态,则读锁请求可以进来,而写锁请求阻塞
- 若当前写锁状态,则读写锁请求都阻塞(并且写锁优先)
三:同步
1. 条件变量
- cond就是信号量,如果值变为不等于0的,则往下执行,而在signal之中所做的是让cond变为1,其中mutex的作用是对于一些上锁的情况,不论当前是什么状态,全部进行解锁。
- 对于等待条件得mutex来说,在它进来的时候,不论是什么状态都会进行解锁操作,而在它参数传出去之后,之前是什么状态依然还是什么状态。
- 最重要的是对于
pthread_cond_wait(&cond,&mutex)
操作来说,我们一定要牢记,使用while,不能够写成if。它会一直等待在这个位置,等到signal信号传递过来,如果使用if则在判断之后就往下继续进行,失去了它得作用。
- 互斥锁:一旦上锁便会堵塞在这里,将当前的状态置成可中断的睡眠状态
- 自旋锁:在lock锁不到的时候,一直在cpu处死循环,我想要的资源到了没(时间短,实时性要求很高)
- gettid的问题:从C库中使用
syscall
函数调用我们想要的,之后再从syscall
中跳转到内核中去,去取出我们想要的。
对于两个线程栈之间的传值问题
2. 消费者模型
四: 线程池
如何选择线程池中应该启动几个线程?
- IO密集型任务,因为cpu闲着,所以可以多开几个线程来进行。
- 计算密集型任务,可以减少线程。
1. 线程池的生产消费者模型
- 头文件
#ifndef __THREADPOOL_H__
#define __THREADPOOL_H__
#include <pthread.h>
// 任务节点
typedef struct task {
void* (*run)(void* arg); // 任务的回调函数
void* arg; // 回调函数的参数
struct task* next;
}task_t;
// 线程池结构体
typedef struct threadpool {
pthread_cond_t cond;
pthread_mutex_t mutex;
task_t* first; // 任务队列队头
task_t* last; // 任务队列队尾指针
int counter; // 线程池中当前有多少个线程
int idle; // 空闲线程个数
int max_thread; // 线程池中线程的上限
int quit; // 线程池销毁标志,默认是0
}threadpool_t;
// 初始化线程池
void threadpool_init(threadpool_t* pool, int max_thread);
// 往线程池中添加任务
void threadpool_add_task(threadpool_t* pool, void* (*run)(void*), void* arg);
// 销毁线程池
void threadpool_destroy(threadpool_t* pool);
#endif //__THREADPOOL_H__
- 接口函数
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#include "threadpool.h"
//线程池的初始化
void threadpool_init(threadpool_t* pool, int max_thread) {
pthread_cond_init(&pool->cond, NULL);
pthread_mutex_init(&pool->mutex, NULL);
pool->first = NULL;
pool->last = NULL;
pool->counter = 0;
pool->idle = 0;
pool->max_thread = max_thread;
pool->quit = 0;
}
//调用函数
void* routine(void* arg) {
threadpool_t* pool = (threadpool_t*)arg;
int timeout = 0;
printf("%p thread created\n", pthread_self());
while (1) {
timeout = 0;
pthread_mutex_lock(&pool->mutex);
pool->idle++; // 刚开始,是一个空闲线程
while (pool->first == NULL && pool->quit == 0) {
struct timespec abstime;
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += 5;
int ret = pthread_cond_timedwait(&pool->cond, &pool->mutex, &abstime);
if (ret == ETIMEDOUT) {
timeout = 1;
printf("%p thread timeout\n", pthread_self());
break;
}
}
pool->idle--; // 得到任务,就不是一个空闲线程
if (pool->first != NULL) { // 有任务,执行任务回调函数
task_t* tmp = pool->first; // 取出任务队列队头节点
pool->first = tmp->next;
pthread_mutex_unlock(&pool->mutex);
tmp->run(tmp->arg);
free(tmp);
pthread_mutex_lock(&pool->mutex);
}
if (pool->first == NULL && timeout == 1) {
pool->counter--;
pthread_mutex_unlock(&pool->mutex);
break;
}
if (pool->first == NULL && pool->quit == 1) {
pool->counter--;
if (pool->counter == 0) { // 最后一个退出的线程,通知threadpool_destroy中的pthread_cond_wait
pthread_cond_signal(&pool->cond);
}
pthread_mutex_unlock(&pool->mutex);
break;
}
pthread_mutex_unlock(&pool->mutex);
}
printf("%p thread exit\n", pthread_self());
}
//线程增加函数
void threadpool_add_task(threadpool_t* pool, void* (*run)(void*), void* arg) {
task_t* new_task = malloc(sizeof(task_t)); // 创建新的任务节点
new_task->run = run;
new_task->arg = arg;
// 往任务队列中添加任务
pthread_mutex_lock(&pool->mutex);
if (pool->first == NULL) {
pool->first = new_task;
}
else {
pool->last->next = new_task;
}
pool->last = new_task;
// 如果有空闲线程,直接唤醒空闲线程,执行当前任务
if (pool->idle > 0) {
pthread_cond_signal(&pool->cond);
}
else if (pool->counter < pool->max_thread) {
// 如果没有空闲线程可以用来执行当前任务,并且没有达到上限,创建新线程
pthread_t tid;
pthread_create(&tid, NULL, routine, pool);
pool->counter++;
}
pthread_mutex_unlock(&pool->mutex);
}
//线程销毁函数
void threadpool_destroy(threadpool_t* pool) {
if (pool->quit == 1)
return;
pthread_mutex_lock(&pool->mutex);
pool->quit = 1;
printf("我要杀了线程池\n");
if (pool->counter > 0) {
// 当线程池中空闲线程个数大于0时,
// pthread_cond_broadcast唤醒所有阻塞在pthread_cond_timedwait上的线程
if (pool->idle > 0)
pthread_cond_broadcast(&pool->cond);
// 如果销毁线程池时,线程池中的线程正在执行任务
// 不会收到broadcast的通知
while (pool->counter > 0)
pthread_cond_wait(&pool->cond, &pool->mutex);
}
pthread_mutex_unlock(&pool->mutex);
pthread_cond_destroy(&pool->cond);
pthread_mutex_destroy(&pool->mutex);
}
- 主函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "threadpool.h"
void* myfun(void* arg) {
int id = *(int*)arg;
free(arg);
printf("id=%d thread_id=%p 开始工作\n", id, pthread_self());
sleep(5);
printf("id=%d thread_id=%p 退出\n", id, pthread_self());
}
int main(void) {
int i;
threadpool_t pool;
threadpool_init(&pool, 3);
for (i = 0; i < 5; i++) {
int* p = malloc(sizeof(int));
*p = i;
threadpool_add_task(&pool, myfun, p);
}
threadpool_destroy(&pool);
}
2. 线程间同步
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define PRO 3
#define COR 2
#define BUFFSZ 5
sem_t full_id;
sem_t empty_id;
pthread_t tid[PRO + COR];
pthread_mutex_t mutex;
int buf[BUFFSZ];
int in = 0;
int out = 0;
int ready = 1;
void* pro(void* arg) {
int id = *(int*)arg;
int i;
while (1) {
sem_wait(&full_id);
pthread_mutex_lock(&mutex);
printf("%d生产者线程开始生产:\n", id);
buf[in] = ready++;
int tmp = in;
in = (in + 1) % BUFFSZ;
pthread_mutex_unlock(&mutex);
sem_post(&empty_id);
printf("%d生产者生产完毕%d产品,装在buf[%d]位置\n", id, ready - 1, tmp);
}
return arg;
}
void* cor(void* arg) {
int id = *(int*)arg;
int i = 0;
while (1) {
sem_wait(&empty_id);
pthread_mutex_lock(&mutex);
printf("%d 消费者线程:\n");
for (i = 0; i < BUFFSZ; i++) {
printf("\tbuf[%d]=%d", i, buf[i]);
if (i == out) {
printf("====> out");
}
printf("\n");
}
out = (out + 1) % BUFFSZ;
pthread_mutex_unlock(&mutex);
sem_post(&full_id);
}
return arg;
}
int main(void) {
int i;
pthread_mutex_init(&mutex, NULL);
sem_init(&full_id, 0, BUFFSZ);
sem_init(&empty_id, 0, 0);
for (i = 0; i < PRO; i++) {
int* p = malloc(sizeof(int));
*p = i;
pthread_create(&tid[i], NULL, pro, p);
}
for (i = 0; i < COR; i++) {
int* p = malloc(sizeof(int));
*p = i;
pthread_create(&tid[PRO + i], NULL, cor, p);
}
for (i = 0; i < PRO + COR; i++) {
int* p = NULL;
pthread_join(tid[i], (void**)&p);
free(p);
}
pthread_mutex_destroy(&mutex);
sem_destroy(&full_id);
sem_destroy(&empty_id);
}
3. 分离线程
大部分情况下,线程都会被做成分离的,如果不做分离的话,在没有join的情况下,会造成资源浪费,而分离情况下的话,线程死亡之后自己就回收了自己的资源,再次创建新的线程其所申请的线程栈空间,便可以被直接拿来使用,不需要和join一样被进行监控。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
typedef struct { char* name; int id; }td_t;
__thread td_t g_data = { "", 0 };
void* r1(void* arg) {
g_data.name = "thread1";
g_data.id = syscall(SYS_gettid);
printf("r1()1: name=%s,id=%d\n", g_data.name, g_data.id);
sleep(2);
printf("r1()2: name=%s,id=%d\n", g_data.name, g_data.id);
}
void* r2(void* arg) {
sleep(1);
g_data.name = "thread2";
g_data.id = syscall(SYS_gettid);
printf("r2()1: name=%s,id=%d\n", g_data.name, g_data.id);
}
int main(void) {
pthread_t t1, t2;
pthread_create(&t1, NULL, r1, NULL);
pthread_create(&t2, NULL, r2, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
}
两个下划线thread
是为了将变量变成线程范围内的全局变量,每个线程都会拥有一份。
五: 线程类
模板方法设计模式,这样可以更好的方便我们对于后期项目的增删改查。
这里的delete是对于线程何时回收的一个问题,对于很多的问题来说,一般情况下,我们只需要管new的东西就够了,而不需要操心delete,就是因为讲delete放置到了回调函数里。
对于lock的封装
实例