C++11实现线程池

news/2024/12/5 1:08:04/

1.所有权的传递

适用移动语义可以将一个unique_lock赋值给另一个unique_lock,适用move实现。

void myThread1()
{unique_lock<mutex> myUnique (testMutex1,std::defer_lock);unique_lock<mutex>myUnique1(std::move(myUnique));//myUnique 则实效 myUnique1 相当于原来的myUnique
}

 std::lock_guard<std::mutex>(testMutex1,std::adopt_lock);  加上了std::adopt_lock就是lock_guard不会再次加锁了。

2.可多次lock的锁  std::recursive_mutex

工程中难免有多个地方要加锁,如果存在交叉调用则会出现异常,递归独占互斥量 std::recursive_mutex 可解决这个问题。

  • 就像互斥锁(mutex)一样,递归互斥锁(recursive_mutex)是可锁定的对象,但它允许同一线程获得对互斥锁对象的多级所有权(多次lock)。
  • 这允许从已经锁定它的线程锁定(或尝试锁定)互斥对象,从而获得对互斥对象的新所有权级别:互斥对象实际上将保持对该线程的锁定,直到调用其成员 unlock 的次数与此所有权级别的次数相同。

3.std::call_once

在多线程执行中,如果希望整个生命周期仅调用一次或者变量仅初始化一次,可以适用call_once.

C++11提供了一个函数 std::call_once(标记(once_flag), 函数名);

头文件在#include <mutex>

#include <iostream>
#include <thread>
#include <string>
#include <mutex>using namespace std;std::once_flag one_flag;void myThread()//定义线程入口函数
{cout << "a value " << endl;
}int main() {std::call_once(one_flag, myThread);std::call_once(one_flag, myThread);return 0;
}

看到这里可能会有些疑惑用互斥锁同样可以实现这个需求,显然互斥锁效率是要低的,因为每次使用这个线程都要上锁然后判断标记位 这样消耗的时间还会更长。

4.线程和协程

 

进程具有独立的内存地址空间。多个线程共用同一个地址空间。

  • 每个线程有自己的栈区,寄存器。
  • 多个线程共享代码段,堆区,全局数据区,打开的文件
  • 共享地址空间

 从操作系统层级上看,虚拟地址空间主要分为两个部分内核区和用户区。

内核区:

  • 内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。
  • 内核总是驻留在内存中,是操作系统的一部分。
  • 系统中所有进程对应的虚拟地址空间的内核区都会映射到同一块物理内存上(系统内核只有一个)。

用户区:存储用户程序运行中用到的各种数据。

 每个进程的虚拟地址空间都是从 0 地址开始的,我们在程序中打印的变量地址也其在虚拟地址空间中的地址,程序是无法直接访问物理内存的。虚拟地址空间中用户区地址范围是 0~3G,里边分为多个区块:

  • 保留区: 位于虚拟地址空间的最底部,未赋予物理地址。任何对它的引用都是非法的,程序中的空指针(NULL)指向的就是这块内存地址。
  • .text段: 代码段也称正文段或文本段,通常用于存放程序的执行代码 (即 CPU 执行的机器指令),代码段一般情况下是只读的,这是对执行代码的一种保护机制。
  • .data段: 数据段通常用于存放程序中已初始化且初值不为 0 的全局变量和静态变量。数据段属于静态内存分配 (静态存储区),可读可写。
  • .bss段: 未初始化以及初始为 0 的全局变量和静态变量,操作系统会将这些未初始化变量初始化为 0
  • 堆(heap):用于存放进程运行时动态分配的内存。堆向高地址扩展 (即 “向上生长”),是不连续的内存区域。这是由于系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。
  • 内存映射区(mmap):作为内存映射区加载磁盘文件,或者加载程序运作过程中需要调用的动态库。
  • 栈(stack): 存储函数内部声明的非静态局部变量,函数参数,函数返回地址等信息,栈内存由编译器自动分配释放。栈和堆相反地址 “向下生长”,分配的内存是连续的。
  • 命令行参数:存储进程执行的时候传递给 main() 函数的参数,argc,argv []
  • 环境变量: 存储和进程相关的环境变量,比如:工作路径,进程所有者等信息。

线程的上下文切换比进程要快的多。切换之前保存当前任务状态。

线程的创建:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
// Compile and link with -pthread, 线程库的名字叫pthread, 全名: libpthread.so libptread.a

参数:

  • thread: 传出参数,是无符号长整形数,线程创建成功,会将线程 ID 写入到这个指针指向的内存中
  • attr: 线程的属性,一般情况下使用默认属性即可,写 NULL
  • start_routine: 函数指针,创建出的子线程的处理动作,也就是该函数在子线程中执行。
  • arg: 作为实参传递到 start_routine 指针指向的函数内部
  • 返回值:线程创建成功返回 0,创建失败返回对应的错误号

 

// pthread_create.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子线程的处理代码
void* working(void* arg)
{printf("我是子线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 创建一个子线程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子线程创建成功, 线程ID: %ld\n", tid);// 2. 子线程不会执行下边的代码, 主线程执行printf("我是主线程, 线程ID: %ld\n", pthread_self());for(int i=0; i<3; ++i){printf("i = %d\n", i);}// 休息, 休息一会儿...// sleep(1);return 0;
}

gcc pthread_create.c -lpthread

动态库名为 libpthread.so 需要使用的参数为 -l,根据规则掐头去尾最终形态应该写成:-lpthread(参数和参数值中间可以有空格)

线程退出

线程退出函数

#include <pthread.h>
void pthread_exit(void *retval);

参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL

线程 | 爱编程的大丙

与临界资源相关的上下文代码块成为临界区。

死锁:枷锁后忘记解锁。重复枷锁,造成死锁。

场景描述:
  1. 有两个共享资源:X, Y,X对应锁A, Y对应锁B
     - 线程A访问资源X, 加锁A
     - 线程B访问资源Y, 加锁B
  2. 线程A要访问资源Y, 线程B要访问资源X,因为资源X和Y已经被对应的锁锁住了,因此这个两个线程被阻塞
     - 线程A被锁B阻塞了, 无法打开A锁
     - 线程B被锁A阻塞了, 无法打开B锁

读写锁:读锁是共享的,写锁是独占的。

调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数的线程会被阻塞


http://www.ppmy.cn/news/62489.html

相关文章

进程替换函数组介绍exec*

目录 前述 execl execlp execle execv execvp execvpe 前述 介绍后缀的意义&#xff1a; l &#xff08;list&#xff09;&#xff1a;表示参数采用列表。 v&#xff08;vector&#xff09;&#xff1a;参数同数组表示。 p&#xff08;path&#xff09;&#xff1a;自…

IP协议基础

文章目录 基本概念IP和TCP分别解决什么问题 以下过程都是在网络层完成的网段划分路由路由转发过程路由表 基本概念 主机: 配有IP地址, 但是不进行路由控制的设备。 路由器: 即配有IP地址, 又能进行路由控制。 节点: 主机和路由器的统称。 IP和TCP分别解决什么问题 TCP解决…

Acwing1293. 夏洛克和他的女朋友

夏洛克有了一个新女友&#xff08;这太不像他了&#xff01;&#xff09;。 情人节到了&#xff0c;他想送给女友一些珠宝当做礼物。 他买了 n 件珠宝&#xff0c;第 i件的价值是 i1&#xff0c;也就是说&#xff0c;珠宝的价值分别为 2,3,…,n1。 华生挑战夏洛克&#xff0…

算法DAY52 动态规划10 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

300.最长递增子序列 五部曲&#xff1a; 1、dp数组的含义&#xff1a; dp[ i ] : 代表 截至到nums[i] (包括 nums[i]) 的序列中&#xff0c;以nums[i] 结尾的&#xff0c;最长递增子序列的长度。这里强调以nums[i] 结尾&#xff0c;是因为还要跟nums[j]做对比&#xff0c;确定…

106.(cesium篇)cesium椎体旋转

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en"> <

互联网摸鱼日报(2023-05-08)

互联网摸鱼日报&#xff08;2023-05-08&#xff09; InfoQ 热门话题 数据库内核杂谈&#xff08;三十二&#xff09;- 杂谈五周年特别篇 比Python快35000倍&#xff01;LLVM&Swift之父宣布全新编程语言Mojo&#xff1a;编程被颠覆了 李彦宏回应文心一言与ChatGPT差距2个…

【Vue学习笔记4】基于Vue3的Composition API + <script setup>

继续前面的学习笔记。 1. 写一个累加器组件 在 src 下的 components 目录下新建一个 Counter.vue &#xff0c;并在这个文件里写出下面的代码&#xff1a; <template><div><h1 click"add">{{ count }}</h1></div> </template>…

代码随想录刷题-栈与队列-删除字符串中的所有相邻重复项

文章目录 删除字符串中的所有相邻重复项习题栈string实现 删除字符串中的所有相邻重复项 本节对应代码随想录中&#xff1a;代码随想录&#xff0c;对应视频链接为&#xff1a;暂无 习题 题目链接&#xff1a;1047. 删除字符串中的所有相邻重复项 - 力扣&#xff08;LeetCod…