1、多线程的发展
早期1998 C++标准版不承认线程的存在,内存模型也没有被正式定义,所以对于1998 C++标准,你没办法在缺少编译器相关扩展的情况下编写多线程应用程序。
早期只能借助编译器厂商提供的平台相关的扩展多线程支持API如WindowsAPI,这就导致了多线程应用的可移植性差。
直到C++11发布将多线程支持纳入C++standard库。
2、并发与并行
并发:同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。
3、什么是线程安全?
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行。
线程安全是另一个值得深究的问题。。。。
4、WindowsAPI
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD:线程安全相关的属性,常置为NULL
SIZE_T dwStackSize,//initialstacksize:新线程的初始化栈的大小,可设置为0
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction:被线程执行的回调函数,也称为线程函数
LPVOID lpParameter,//threadargument:传入线程函数的参数,不需传递参数时为NULL
DWORD dwCreationFlags,//creationoption:控制线程创建的标志
LPDWORD lpThreadId//threadidentifier:传出参数,用于获得线程ID,如果为NULL则不返回线程ID
)
在MFC中也可以直接用
AfxBeginThread();
MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程,区别在于用户界面线程能处理消息响应,而工作者线程不能。其原型和过程:
用户界面线程的AfxBeginThread原型如下:
CWinThread* AFXAPI AfxBeginThread
(
CRuntimeClass* pThreadClass,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs
)
其中:
参数1是从CWinThread派生的RUNTIME_CLASS类;
参数2指定线程优先级,如果为0,则与创建该线程的线程相同;
参数3指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;
参数4是一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。
参数5表示线程的安全属性。
工作者线程的AfxBeginThread ,原型如下:
CWinThread* AFXAPI AfxBeginThread
(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs
)
其中:
参数1 线程的入口函数,声明一定要如下: UINT MyThreadFunction( LPVOID pParam );
参数2 传递入线程的参数,类型为LPVOID,所以我们可以传递一个结构体入线程.
参数3、4、5分别指定线程的优先级、堆栈大小、创建标识、安全属性,含义同用户界面线程。
AfxBeginThread除前面两个参数外,后面几个都是默认参数,可以省略。前两个参数中,第一个是线程函数的指针,第二个是传递给这个函数的参数。
实际中我们经常这样用:
AfxBeginThread(ThreadProc,this);
把this传过去,线程函数就可以使用和操作类的成员了。
例如:
线程函数声明为全局函数:
UINT _cdecl thread_begin(LPVOID pParam)
{
//将传入的参数转回来
CSDK_TestDlg *dlg = (CSDK_TestDlg*)pParam;
VideoCapture videocap;
Mat src,dst;
videocap.open(0);
while (true)
{
videocap.read(src);
int NewWidth = src.cols;
int NewHeight = src.rows;
double XScale = double(dlg->rect.right - dlg->rect.left) / double(src.cols);
double YScale = double(dlg->rect.bottom - dlg->rect.top) / double(src.rows);
NewWidth = src.cols * XScale;
NewHeight = src.rows * YScale;
cv::Size NewSize(NewWidth, NewHeight);
cv::resize(src, dst, NewSize);
cv::imshow("view", dst);
waitKey(20);
}
}
在CSDK_TestDlg类里面使用
AfxBeginThread(thread_begin, this, 0, THREAD_PRIORITY_NORMAL, 0);
//this指针指向的是当前Dlg的句柄
注意:
线程函数是静态类成员函数或者全局函数。
线程函数的声明必须为: UINT MyThreadFunction( LPVOID pParam );
5、std库thread的简单操作
多线程编程与普通编程唯一真正的区别在于某些函数可以并发运行,所以你需要确保共享数据的并发访问是安全的。为了并发地运行函数,必须使用特定的函数以及对象来管理各个线程。
前面一篇文章提到过C++11的多线程问题,代码我就直接复制粘贴以前的了;
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
void f1(int n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 1 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void f2(int& n)
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 2 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
class foo
{
public:
void bar()
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 3 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int n = 0;
};
class baz
{
public:
void operator()()
{
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 4 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int n = 0;
};
int main()
{
int n = 0;
foo f;
baz b;
std::thread t1; // t1 is not a thread
std::thread t2(f1, n + 1); // 值传递
std::thread t3(f2, std::ref(n)); // 引用传递
std::thread t4(std::move(t3)); // t4运行t3,t3不再运行
std::thread t5(&foo::bar, &f); // t5 在对象f上运行foo类的bar()函数(函数地址跟对象地址都传进去了)
std::thread t6(b);
// t6 在对象 b 的一个副本上运行baz类的operator()()函数
//重载运算符(operator())
t2.join();
t4.join();
t5.join();
t6.join();
std::cout << "Final value of n is " << n << '\n';
std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';
std::cout << "Final value of b.n (baz::n) is " << b.n << '\n';
}