多线程(multithreading),是指在软件或者硬件上实现多个线程并发执行的技术。具有多核CPU的支持的计算机能够真正在同一时间执行多个程序片段,进而提升程序的处理性能。在一个程序中,这些独立运行的程序片段被称为“线程”(Thread),利用其编程的概念就叫作“多线程处理”。
进程是指一个程序的运行实例,而线程是指进程中独立的执行流程。一个进程可以有多个线程,多个线程之间可以并发执行。
C++支持多线程编程,主要使用的是线程库<thread>
。
示例1: 创建线程使用std::thread
类
#include <iostream>
#include <thread> //必须包含<thread>头文件
void threadFunctionA()
{
std::cout << "Run New thread: 1" << std::endl;
}
void threadFunctionB(int n)
{
std::cout << "Run New thread: "<< n << std::endl;
}
int main()
{
std::cout << "Run Main Thread" << std::endl;
std::thread newThread1(threadFunctionA);
std::thread newThread2(threadFunctionB,2);
newThread1.join();
newThread2.join();
return 0;
}
//result
Run Main Thread
Run New thread: 1
Run New thread: 2
上述示例中,我们创建了两个线程newThread1和newThread2,使用函数threadFunctionA()
和threadFunctionB()
作为线程的执行函数,并使用join()
函数等待线程执行完成。
示例2: 执行函数有引用参数
#include <iostream>
#include <thread> //必须包含<thread>头文件
void threadFunc(int &arg1, int arg2)
{
arg1 = arg2;
std::cout << "arg1 = " << arg1 << std::endl;
}
int main()
{
std::cout << "Run Main Thread!" << std::endl;
int a = 1;
int b = 5;
std::thread newTh(threadFunc, a, b); //此处会报错
newTh.join();
return 0;
}
注意: 若编译上述代码,编译器会报如下错误:
错误 C2672 “std::invoke”: 未找到匹配的重载函数
错误 C2893 未能使函数模板“unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)”专用化
这是因为thread在传递参数时,是以右值传递的,如果要传递一个左值可以使用std::ref
和std::cref
std::ref
可以包装按引用传递的值为右值。std::cref
可以包装按const
引用传递的值为右值。因此,示例2代码可修改为:
#include <iostream>
#include <thread> //必须包含<thread>头文件
void threadFunc(int &arg1, int arg2)
{
arg1 = arg2;
std::cout << "New Thread arg1 = " << arg1 << std::endl;
}
int main()
{
std::cout << "Run Main Thread!" << std::endl;
int a = 1, b = 5;
std::thread newTh(threadFunc, std::ref(a), b); //使用ref
newTh.join();
return 0;
}
//result
Run Main Thread!
arg1 = 5
在C++中,创建了一个线程时,它通常被称为一个可联接(joinable)
的线程,可以通过调用join()
函数或detach()
函数来管理线程的执行。
方法 | 说明 | |
---|---|---|
1 | join() | 等待一个线程完成,如果该线程还未执行完毕,则当前线程(一般是主线程)将被阻塞,直到该线程执行完成,主线程才会继续执行。 |
2 | detach() | 将当前线程与创建的线程分离,使它们分别运行,当分离的线程执行完毕后,系统会自动回收其资源。如果一个线程被分离了,就不能再使用join() 函数了,因为线程已经无法被联接了。 |
3 | joinable() | 判断线程是否可以执行join() 函数,返回true/false |
示例3:
#include <iostream>
#include <thread>
#include <windows.h>
void foo()
{
std::cout << "Run New thread!\n";
Sleep(2000); //需要头文件<windows.h>
}
int main()
{
std::thread t(foo);
if (t.joinable())
{
t.join(); // 等待线程t执行完毕
// t.detach(); // 分离线程t与主线程
}
std::cout << "Run Main thread!\n";
return 0;
}
在上述的示例中,创建了一个可联接的线程t
,使用t.join()
主线程将被阻塞,直到t
线程执行完毕。如果使用t.detach()
将t
线程分离,那么它们将同时执行,主线程将不会阻塞。
注意:
join()
函数时才执行的,调用join()
函数只是阻塞等待线程结束并回收资源。detach()
的线程)会在调用它的线程结束或自己结束时自动释放资源。join()
或detach()
的线程在程序结束时会引发异常。在C++中,this_thread
类提供了一些关于当前线程的功能函数。具体如下:
使用 | 说明 | |
---|---|---|
1 | std::this_thread::sleep_for() | 当前线程休眠指定的时间 |
2 | std::this_thread::sleep_until() | 当前线程休眠直到指定时间点 |
3 | std::this_thread::yield() | 当前线程让出CPU,允许其他线程运行 |
4 | std::this_thread::get_id() | 获取当前线程的ID |
此外,this_thread
还包含重载运算符==
和!=
,用于比较两个线程是否相等。
示例4:
#include <iostream>
#include <thread>
#include <chrono>
void my_thread()
{
std::cout << "Thread " << std::this_thread::get_id() << " start!" << std::endl;
for (int i = 1; i <= 5; i++)
{
std::cout << "Thread " << std::this_thread::get_id() << " running: " << i << std::endl;
std::this_thread::yield(); // 让出当前线程的时间片
std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 线程休眠200毫秒
}
std::cout << "Thread " << std::this_thread::get_id() << " end!" << std::endl;
}
int main()
{
std::cout << "Main thread id: " << std::this_thread::get_id() << std::endl;
std::thread t1(my_thread);
std::thread t2(my_thread);
t1.join();
t2.join();
return 0;
}
//result 程序输出的结果可能如下:
Main thread id: 43108
Thread 39272 start!
Thread 33480 start!
Thread 33480 running: 1
Thread 39272 running: 1
Thread 33480 running: 2
Thread 39272 running: 2
Thread 33480 running: 3
Thread 39272 running: 3
Thread 33480 running: 4
Thread 39272 running: 4
Thread 39272 running: 5
Thread 33480 running: 5
Thread 39272 ends
Thread 33480 ends
在多线程编程中,需要注意以下问题:
std::mutex
是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁。
方法 | 说明 | |
---|---|---|
1 | lock() | 将mutex上锁。如果mutex已经被其它线程上锁,那么会阻塞,直到解锁;如果mutex已经被同一个线程锁住,那么会产生死锁。 |
2 | unlock() | 将mutex解锁,释放其所有权。如果有线程因为调用lock() 不能上锁而被阻塞,则调用此函数会将mutex的主动权随机交给其中一个线程;如果mutex不是被此线程上锁,那么会引发未定义的异常。 |
3 | try_lock() | 尝试将mutex上锁。如果mutex未被上锁,则将其上锁并返回true;如果mutex已被锁则返回false。 |
示例: 使用互斥量
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int num = 0;
void thread_function(int &n)
{
for (int i = 0; i < 100; ++i)
{
mtx.lock();
n++;
mtx.unlock();
}
}
int main()
{
std::thread myThread[500];
for (std::thread &a : myThread)
{
a = std::thread(thread_function, std::ref(num));
a.join();
}
std::cout << "num = " << num << std::endl;
std::cout << "Main thread exits!" << std::endl;
return 0;
}
//result
num = 50000
Main thread exits!
注意: 在使用互斥量时,需要注意以下问题:
std::mutex
来控制对共享资源的访问,因此可能会对程序的性能造成影响,如果需要优化程序性能,可以考虑使用无锁编程等技术。std::lock_guard
是C++标准库中的一个模板类,用于实现资源的自动加锁和解锁。它是基于RAII(资源获取即初始化)的设计理念,能够确保在作用域结束时自动释放锁资源,避免了手动管理锁的复杂性和可能出现的错误。
std::lock_guard
的主要特点如下:
std::lock_guard
对象时,会立即对指定的互斥量进行加锁操作。这样可以确保在进入作用域后,互斥量已经被锁定,避免了并发访问资源的竞争条件。std::lock_guard
对象在作用域结束时,会自动释放互斥量。无论作用域是通过正常的流程结束、异常抛出还是使用return
语句提前返回,std::lock_guard
都能保证互斥量被正确解锁,避免了资源泄漏和死锁的风险。std::lock_guard
是通过栈上的对象实现的,因此适用于在局部范围内锁定互斥量。当超出std::lock_guard
对象的作用域时,互斥量会自动解锁,释放控制权。使用std::lock_guard
的一般步骤如下:
std::lock_guard
对象,传入要加锁的互斥量作为参数。std::lock_guard
对象的作用域结束时,自动调用析构函数解锁互斥量。示例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥量
void thread_function()
{
std::lock_guard<std::mutex> lock(mtx); // 加锁互斥量
std::cout << "Thread running" << std::endl;
// 执行需要加锁保护的代码
} // lock_guard对象的析构函数自动解锁互斥量
int main()
{
std::thread t1(thread_function);
t1.join();
std::cout << "Main thread exits!" << std::endl;
return 0;
}
在上述示例中,std::lock_guard
对象lock
会在thread_function
中加锁互斥量,保护了输出语句的执行。当thread_function
结束时,lock_guard
对象的析构函数会自动解锁互斥量。这样可以确保互斥量在合适的时候被锁定和解锁,避免了多线程间的竞争问题。
总而言之,std::lock_guard
提供了一种简单而安全的方式来管理互斥量的锁定和解锁,使多线程编程更加方便和可靠。
std::unique_lock
是C++标准库中的一个模板类,用于实现更加灵活的互斥量的加锁和解锁操作。它提供了比std::lock_guard
更多的功能和灵活性。
std::unique_lock
的主要特点如下:
自动加锁和解锁: 与std::lock_guard
类似,std::unique_lock
在创建对象时立即对指定的互斥量进行加锁操作,确保互斥量被锁定。在对象的生命周期结束时,会自动解锁互斥量。这种自动加锁和解锁的机制避免了手动管理锁的复杂性和可能出现的错误。
支持灵活的加锁和解锁: 相对于std::lock_guard
的自动加锁和解锁,std::unique_lock
提供了更灵活的方式。它可以在需要的时候手动加锁和解锁互斥量,允许在不同的代码块中对互斥量进行多次加锁和解锁操作。
支持延迟加锁和条件变量:std::unique_lock
还支持延迟加锁的功能,可以在不立即加锁的情况下创建对象,稍后根据需要进行加锁操作。此外,它还可以与条件变量(std::condition_variable
)一起使用,实现更复杂的线程同步和等待机制。
使用std::unique_lock
的一般步骤如下:
std::unique_lock
对象,传入要加锁的互斥量作为参数。lock
函数对互斥量进行加锁,或者在需要时调用unlock
函数手动解锁互斥量。示例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥量
void thread_function()
{
std::unique_lock<std::mutex> lock(mtx); // 加锁互斥量
std::cout << "Thread running" << std::endl;
// 执行需要加锁保护的代码
lock.unlock(); // 手动解锁互斥量
// 执行不需要加锁保护的代码
lock.lock(); // 再次加锁互斥量
// 执行需要加锁保护的代码
}
// unique_lock对象的析构函数自动解锁互斥量
int main()
{
std::thread t1(thread_function);
t1.join();
std::cout << "Main thread exits!" << std::endl;
return 0;
}
在上述示例中,std::unique_lock
对象lock
会在创建时自动加锁互斥量,析构时自动解锁互斥量。我们可以通过调用lock
和unlock
函数手动控制加锁和解锁的时机,以实现更灵活的操作。
总而言之,std::unique_lock
提供了更灵活和功能丰富的互斥量的加锁和解锁机制,使多线程编程更加便捷和安全。它在处理复杂的同步需求、延迟加锁以及与条件变量的结合等方面非常有用。
std::condition_variable
是C++标准库中的一个类,用于在多线程编程中实现线程间的条件变量和线程同步。它提供了等待和通知的机制,使得线程可以等待某个条件成立时被唤醒,或者在满足某个条件时通知其他等待的线程。其提供了以下几个函数用于等待和通知线程:
方法 | 说明 | |
---|---|---|
1 | wait | 使当前线程进入等待状态,直到被其他线程通过notify_one() 或notify_all() 函数唤醒。该函数需要一个互斥锁作为参数,调用时会自动释放互斥锁,并在被唤醒后重新获取互斥锁。 |
2 | wait_for | wait_for() : 使当前线程进入等待状态,最多等待一定的时间,直到被其他线程通过notify_one() 或notify_all() 函数唤醒,或者等待超时。该函数需要一个互斥锁和一个时间段作为参数,返回时有两种情况:等待超时返回std::cv_status::timeout ,被唤醒返回std::cv_status::no_timeout 。 |
3 | wait_until | wait_until() : 使当前线程进入等待状态,直到被其他线程通过notify_one() 或notify_all() 函数唤醒,或者等待时间达到指定的绝对时间点。该函数需要一个互斥锁和一个绝对时间点作为参数,返回时有两种情况:时间到达返回std::cv_status::timeout ,被唤醒返回std::cv_status::no_timeout 。 |
4 | notify_one | notify_one() : 唤醒一个等待中的线程,如果有多个线程在等待,则选择其中一个线程唤醒。 |
5 | notify_all | notify_all() : 唤醒所有等待中的线程,使它们从等待状态返回。 |
std::condition_variable
的主要特点如下:
等待和通知机制:std::condition_variable
允许线程进入等待状态,直到某个条件满足时才被唤醒。线程可以调用wait
函数进入等待状态,并指定一个互斥量作为参数,以确保线程在等待期间互斥量被锁定。当其他线程满足条件并调用notify_one
或notify_all
函数时,等待的线程将被唤醒并继续执行。
与互斥量配合使用:std::condition_variable
需要与互斥量(std::mutex
或std::unique_lock<std::mutex>
)配合使用,以确保线程之间的互斥性。在等待之前,线程必须先锁定互斥量,以避免竞争条件。当条件满足时,通知其他等待的线程之前,必须再次锁定互斥量。
支持超时等待:std::condition_variable
提供了带有超时参数的等待函数wait_for
和wait_until
,允许线程在等待一段时间后自动被唤醒。这对于处理超时情况或限时等待非常有用。
使用std::condition_variable
的一般步骤如下:
std::condition_variable
对象。std::mutex
或std::unique_lock<std::mutex>
)。std::unique_lock
锁定互斥量,并调用wait
函数进入等待状态。std::unique_lock
锁定互斥量,并调用notify_one
或notify_all
函数通知等待的线程。示例:
#include <iostream>
#include <thread>
#include <condition_variable>
std::mutex mtx; // 互斥量
std::condition_variable cv; // 条件变量
bool isReady = false; // 条件
void thread_function()
{
std::unique_lock<std::mutex> lock(mtx);
while (!isReady)
{
cv.wait(lock); // 等待条件满足
}
std::cout << "Thread is notified" << std::endl;
}
int main()
{
std::thread t(thread_function);
// 模拟一段耗时操作
std::this_thread::sleep_for(std::chrono::seconds(2));
{
std::lock_guard<std::mutex> lock(mtx);
isReady = true; // 设置条件为true
}
cv.notify_one(); // 通知等待的线程
t.join();
return 0;
}
上述示例中,创建了一个线程,该线程在等待状态下通过cv.wait(lock)
等待条件满足。主线程经过一段时间后将条件设置为true
,然后通过cv.notify_one()
通知等待的线程。等待的线程被唤醒后输出一条消息。
std::mutex
可以很好地解决多线程资源争抢的问题,但它每次循环都要加锁、解锁,这样固然会浪费很多的时间。
在 C++ 中,std::atomic
是用来提供原子操作的类,atomic,本意为原子,原子操作是最小的且不可并行化的操作。这就意味着即使是多线程,也要像同步进行一样同步操作原子对象,从而省去了互斥量上锁、解锁的时间消耗。
使用 std::atomic
可以保证数据在操作期间不被其他线程修改,这样就避免了数据竞争,使得程序在多线程并发访问时仍然能够正确执行。
示例:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic> //必须包含
std::atomic_int num = 0;
void thread_function(std::atomic_int &n) //修改类型
{
for (int i = 0; i < 100; ++i)
{
n++;
}
}
int main()
{
std::thread myThread[500];
for (std::thread &a : myThread)
{
a = std::thread(thread_function, std::ref(num));
a.join();
}
std::cout << "num = " << num << std::endl;
std::cout << "Main thread exits!" << std::endl;
return 0;
}
//result
num = 50000
Main thread exits!
说明:std::atomic_int
是std::atomic<int>
的别名。
文章浏览阅读2.6k次,点赞4次,收藏21次。pyltp环境的搭建:在终端安装pyltp时一般会报错:使用wheel方法:下载wheels下面两个文件针对不同的python版本下载一个即可python-3.5: pyltp-0.2.1-cp35-cp35m-win_amd64.whlhttps://pan.baidu.com/s/1Ekx3dHVzt5raXtiuH-S9qwpython-3.6: pyltp-0.2.1-..._pyltp 人名检测
文章浏览阅读1.3k次。学习内容进一步熟悉IIC接口的开发调试,上次工程只是模拟开发,本次将进行实践操作。开发环境vivado 18.3SDKpynq-z2硬件平台搭建参考前文-ZYNQ-实现外设驱动开发-iic接口的光强度传感器GY-30的搭建过程下面给出部分更改操作:这里我们需要把之前选到的MIO改成EMIO同时参考我们的pynq的电路图:进行管脚分配,这里我开始把管脚分配到了AR_SCL和AR_SDA上,试了很多次都不行,然后我就改到了相邻的AR12和A13上,就能正常工作了,这里应该是之前电平一直_zynq模拟iic
文章浏览阅读4.3k次,点赞4次,收藏40次。挖矿病毒特征:“挖矿”病毒是一段恶意代码或者一个软件,一般利用主机或者操作系统的高危漏洞术在局域网内传播,控制电脑进行大量的计算机运算来获取虚拟货币。该病毒会消耗大量的计算机处理资源,常见的就是系统中毒后系统CPU占用接近100%、系统卡顿执行基本命令响应缓慢、系统出现异常进程无法正常kill、系统内存异常占用不稳定等。常见攻击方式:不明邮件附件、文件、连接和网页、不明U盘随意接入、非官方软件和服务器弱口令、高危端口暴露等事件大概处置流程:详细流程、操作命令。_wmic process get caption,commandline /value >> tmp.txt
文章浏览阅读285次。poj2240题目:想利用美元套利,就是100美元->50英镑->500发廊->105美元问有没有这种路径能利用美元套利思路:①map处理②spfa判正环,咦?spfa不是判负环吗,怎么判正环啊,也是个思维哦!③一些写法,也可以用链式前向星,我用的vector邻接表,因为这个题最大N为30,只要写对,不用关心算复杂度了#include <iostream&g...
文章浏览阅读1.5w次,点赞38次,收藏47次。软件工程是一门研究和应用如何以系统化、规范化、可量化的方法开发和维护软件的学科。它涉及到软件开发的全过程,包括需求分析、设计、编码、测试、部署和维护等阶段。软件工程的目标是以最大限度地提高软件的质量、可靠性、可维护性和可重用性,同时控制软件开发的成本、进度和风险。软件工程包括许多技术和工具的应用,如需求工程、面向对象的分析和设计、软件测试、软件项目管理等。同时,它还涉及到与用户和其他相关人员的沟通和协作,以确保软件能够满足用户的需求。
文章浏览阅读121次。导读世界上最流行的开源网络协议分析器Wireshark已经升级到2.6版本的新稳定版本,这是一个重大更新,增加了许多新功能和改进,并支持新协议。自从Wireshark 2.5以来,已经进行了许多用户界面改进,而Wireshark 2.6似乎是支持传统GTK+图形用户界面的最后一个版本,正如开发团队宣布它在下一个Wireshark 3.0主要系列中不会得到支持,Wireshark 2.6的新功能包括..._linux是否支持twamp
文章浏览阅读575次。如果你研究SSL和加密的时间足够长,最终你会遇到“密码”这个词。除了通常是一个很酷的词外,密码是加密的一个非常重要的组成部分。那么,加密所使用的密码是什么呢?密码就是算法,更具体地说,密码是执行加密和相应的解密过程的一系列步骤。现在的密码依赖于计算机的先进处理能力。然而,情况并非总是如此。历史上最早的著名密码之一属于凯撒大帝——罗马皇帝和开胃菜沙拉的供应者,在军事行动中,他使用这些密码与他的将军们..._tls psk
文章浏览阅读2.2k次。程序绑定的概念: 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定静态绑定: 在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。 针对java简单的可以理解为程序编译期的绑定;这里特别说明一点,java当中的方法只有final,static,private和构造方法是前期绑定动态绑定: 后期_java静态绑定概念
文章浏览阅读571次,点赞5次,收藏4次。就最近和各位大佬认识下来,以前觉得学习go语言,可能资料比较少,可是后来才发现,原来资料并不少,甚至可以说通过大家的努力,go社区已经非常包容且完善了接下来会推荐一些资料,以及大佬社区微软go语言中文网Gopher China golang中国LearnKu自建博客:boyacch码农桃花源七月天面向信仰编程less is betterPure White煎鱼mzh鸟窝峰云就她了luozhiyun`s BlogVincent Blanchon地鼠导航go夜读g_七米 golang
文章浏览阅读1.7w次。时间:2016-08-30作者:admin 阅读:次-c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。下面的参数是根据需要在压缩或解压档案时可选的。下面的参数-f 是必须的-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。# tar -cf..._tar怎么解压zip文件
文章浏览阅读184次。今天小编给大家带来一个优秀妹子的后台面试经验总结,希望对正在面试或者以后需要面试的人提供一些参考和帮助。具体如下:本人妹子,985 硕士,211 本科,专业都是软件工程,一直投的是 Java 后台开发,只投过一次网易的测试,技术不是大牛,但是比较努力。实验室没有项目,so 项目经验是 0,在去年这个时候看到实验室师兄找工作的艰难,因此开始复习的时间比较早。最开始先看的 java 基础,看的马某某的视频,后面就看框架视频,后来也看过某某学院的视频,都是在网上找的免费的。..._双非女后端大厂面经
文章浏览阅读593次。 具体代码是:int charTowchar(char* pSrc, wchar_t* pDest){ if (pSrc == NULL || pDest == NULL) { return 0; } setlocale(LC_CTYPE, "zh_CN.utf8"); int w_size = mbstowcs(NULL, pSrc, 0) + 1; //w_size=0说明出错了。可能有非法字符,也可能是locale设置不对。_char 转换成 wchar_t