多线程相关
线程与进程
- cpu:多个核心就能并行多个任务
- 进程
进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。 - 线程
线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。 - 进程管着多个线程。
并发和并行
并行(parallellism)指的是多个任务在同一时刻同时在执行。
并发(concurrency)是指在一个时间段内,多个任务交替进行。虽然看起来像在同时执行,但其实是交替的。
多线程管理
- 每个线程都需要一个入口函数,入口函数返回退出,该线程也会退出,主线程就是以main函数作为入口函数的线程。不太懂入口函数
- detach和join
1.detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
2.join方式,等待关联的线程完成,才会继续执行join()后的代码。
3.在以detach的方式执行线程时,要将线程访问的局部数据复制到线程的空间(使用按值传递),一定要确保线程没有使用局部变量的引用或者指针,除非你能肯定该线程会在局部作用域结束前执行结束。
4.调用类成员函数
5.转移线程的所有权
6.线程标识的获取
线程的标识类型为std::thread::id,有两种方式获得到线程的id:1.通过thread的实例调用get_id()直接获取;2.在当前线程上调用this_thread::get_id()获取。
9.2.6 线程暂停
7 异常情况下等待线程完成
为了避免主线程出现异常时将子线程终结,就要保证子线程在函数退出前完成,即在函数退出前调用join()。
方法一:异常捕获
void func() {
thread t([]{
cout << "hello C++ 11" << endl;
});
try
{
do_something_else();
}
catch (...)
{
t.join();
throw;
}
t.join();
}
线程的同步与互斥
Windows下的
- 临界区
认为全局变量为共享资源,线程要访问共享资源得申请临界区。
#include "stdafx.h"
#include<windows.h>
#include<iostream>
using namespace std;
int number = 1; //定义全局变量
CRITICAL_SECTION Critical; //声明临界区对象,初始化对象在main中
unsigned long __stdcall ThreadProc1(void* lp)//无符号长整型
//以下是 __stdcall:
//表示函数的调用约定。调用约定是一组编译器约定,用于确定如何在调用函数时传递参数、以及谁(调用者还是被调用者)负责清除堆栈。
//在Windows平台上,__stdcall是一种常见的调用约定,通常用于Win32 API函数。
//对于线程处理函数,通常需要使用__stdcall调用约定,以满足Windows线程API(如CreateThread和_beginthreadex)的要求。
{
while (number < 100)
{
EnterCriticalSection(&Critical);//请求临界区对象
cout << "thread 1 :"<<number << endl;
++number;
_sleep(100);
LeaveCriticalSection(&Critical);//解锁临界区对象
}
return 0;
}
unsigned long __stdcall ThreadProc2(void* lp)
{
while (number < 100)
{
EnterCriticalSection(&Critical);
cout << "thread 2 :"<<number << endl;
++number;
_sleep(100);
LeaveCriticalSection(&Critical);
}
return 0;
}
int main()
{
InitializeCriticalSection(&Critical); //初始化临界区对象
CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);//createthread 是windows API用于创建线程函数的
CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
Sleep(10*1000);
system("pause");
return 0;
}
- 事件
我的理解:事件是一个对象,它有信号和非信号状态,一个线程可以等待对象有状态后再执行,,也可以设置这个状态,当一个等待线程被解除阻塞时(因为事件变为有信号状态),事件对象会立即重置为非信号状态(自动重置事件)
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//创建一个事件对象并获取其句柄
SetEvent(hEvent);//使用SetEvent函数将事件设置为有信号状态
DWORD result = WaitForSingleObject(hEvent, INFINITE);//线程可以使用WaitForSingleObject,函数等待事件对象变为有信号状态:
CloseHandle(hEvent);//最后,不要忘记在不再需要事件对象时使用CloseHandle函数关闭事件句柄:
- 信号量
我的理解:它和事件的区别与,信号量允许一定数量的线程访问。
hSemaphore = CreateSemaphore(NULL, 1, 100, "sema");//创建
WaitForSingleObject(hSemaphore, INFINITE); //等待信号量为有信号状态
ReleaseSemaphore(hSemaphore, 1, &count);//释放
c++下的锁
- 互斥锁mutex ,还有很方便的std::lock_guard类(自动锁定锁),unique_guard类(可以不锁定,也可以在代码中锁定)
std::mutex mtx;
mtx.lock()
do_something...; //共享的数据
mtx.unlock();
- 条件锁(condition_variable ):等待通知
- 自旋锁:互斥锁会让CPU去处理其他的任务,而自旋锁则会让CPU一直不断循环请求获取这个锁。
- 原子操作
原子操作(atomic operations)是指在多线程环境中,对共享数据的一系列操作,这些操作在执行过程中不会被其他线程打断。原子操作的主要目的是避免并发问题,如竞态条件(race conditions)和数据不一致。
atomic类提供了原子操作
例子:
counter的原来的int型,改为atomic_int型就可以了