[Linux系列]Linux线程与线程池学习脉络梳理,重点知识模块总结记忆

 4

一个进程内部的控制序列,被称之为“线程”(一切进程至少都存在一个执行线程),线程在进程内部的运行,本质是在进程地址空间内运行。

一:线程

  1. 哲学家进餐问题
    哲学家就餐
    这也是我们学习线程时,一个较为知名的例子,就是哲学家进餐和公交售票,生产消费模型了,所以可以通过这样的一个例子来更好的加深对于线程的了解。
  2. 线程
    多线程
    进程若是创建100个的话,则pcb和mmap都增加100个;若线程增加100个的话,pcb增加100个,但内存空间mmap不增加,两者的vm里都会增加线程栈。
  3. 线程创建函数
    线程函数
    线程创建若返回零则表示成功,否则返回的就直接是它的错误号。
  4. 线程id
    线程id
    self所能够获取到的线程id是线程栈的首地址(进行进程范围内的比较)
    在这里插入图片描述
    在这里插入图片描述
  • pid_t pid 线程id
  • pid_t tgid 进程组id
  1. 进程和线程函数的比较
    进程和线程
  • 能够杀死进程的kill却无法杀死线程,因为kill函数所传递的参数会先被线程进行接收,如果县城无法接收的话则会被它所再得进程给接收,是无法直接将线程杀死的;而对于线程得cancel函数,不是直接取消掉,而是达到一个点才会进行取消。

  • 使用join的话,可以回收当前它所申请的空间, 或者是如果再创建线程的话会覆盖掉之前结束掉的线程所开的空间,而如果不加join的话,则会不断的去申请新的线程栈空间,造成了资源的泄露。

  • 如果用exit来结束线程的话,main函数是不是也会结束呢?
    exit结束线程
    这样的情况下,主函数退出了,但线程却没有退出而成为了僵尸状态;而多线程的情况下,僵尸是可以被杀死的。

到底能够开多少线程,受到这三个因素的影响:
在这里插入图片描述

二:互斥量

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则在判断之后就往下继续进行,失去了它得作用。
    在这里插入图片描述
    在这里插入图片描述
  1. 互斥锁:一旦上锁便会堵塞在这里,将当前的状态置成可中断的睡眠状态
  2. 自旋锁:在lock锁不到的时候,一直在cpu处死循环,我想要的资源到了没(时间短,实时性要求很高)
  3. gettid的问题:从C库中使用syscall函数调用我们想要的,之后再从syscall中跳转到内核中去,去取出我们想要的。
    在这里插入图片描述
    对于两个线程栈之间的传值问题
    在这里插入图片描述
    在这里插入图片描述

2. 消费者模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四: 线程池

如何选择线程池中应该启动几个线程?

  • IO密集型任务,因为cpu闲着,所以可以多开几个线程来进行。
  • 计算密集型任务,可以减少线程。

1. 线程池的生产消费者模型

在这里插入图片描述代码解读

  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__
  1. 接口函数
#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);
}
  1. 主函数
#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的封装

在这里插入图片描述

实例
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Luckily0818/article/details/107509774