一、thread

 

本着能用STL就不用其他库的原则,本文以c++11的std::thread作为接口开发。【VS2010不支持C++11的线程,VS2013支持】

 

 

根据我另一个帖子,线程函数只能是全局或者静态的。 

全局函数:只能访问全局变量,需要注意加互斥锁

静态函数:只能访问静态变量

 

项目中常常需要线程访问类中的成员,本文也重点实现这个功能,下面提供两种方式。使用多线程卖票举例:

卖票头文件:



#pragma once
#include <thread>
#include <mutex>
#include <Windows.h>
using namespace std;
class SellTickts
{
public:
void SellTickts::seller_1();
void SellTickts::seller_2();
int ticketNum = 10;
mutex Mutex;
public:
SellTickts();
virtual ~SellTickts();
void sell();
friend void sell(void* param);
};


卖票源文件:



#include "stdafx.h"
#include "SellTickts.h"


SellTickts::SellTickts()
{

}


SellTickts::~SellTickts()
{

}
//函数名称:售票员1
//函数作用:NULL
//函数参数:NULL
//函数返回值:NULL
//函数信息:NULL
//备注:NULL
void SellTickts::seller_1()
{
for (;;)
{
Mutex.lock();
if (ticketNum == 0)
{
break;
}
else
{

ticketNum--;
printf("Seller_1 sold a ticket, remains %d.\n", ticketNum);
if (ticketNum == 0)
{
printf("Tickets has been sold out.\n");
break;
}
}
Mutex.unlock();
Sleep(100);
}
}

//函数名称:售票员2
//函数作用:NULL
//函数参数:NULL
//函数返回值:NULL
//函数信息:NULL
//备注:NULL
void SellTickts::seller_2()
{
for (;;)
{
Mutex.lock();
if (ticketNum == 0)
{
break;
}
else
{

ticketNum--;
printf("Seller_2 sold a ticket, remains %d.\n", ticketNum);
if (ticketNum == 0)
{
printf("Tickets has been sold out.\n");
break;
}
}
Mutex.unlock();
Sleep(100);
}
}

//函数名称:类函数售票
//函数作用:NULL
//函数参数:NULL
//函数返回值:NULL
//函数信息:NULL
//备注:NULL
void SellTickts::sell()
{
printf("class sell.\n");
thread thread1(&SellTickts::seller_1,this);
thread thread2(&SellTickts::seller_2,this);
thread1.detach();
thread2.detach();
}

//函数名称:友元函数售票
//函数作用:NULL
//函数参数:NULL
//函数返回值:NULL
//函数信息:NULL
//备注:NULL
void sell(void* param)
{
printf("friend sell.\n");
SellTickts* pThis = (SellTickts*)param;
thread thread1(&SellTickts::seller_1, pThis);
thread thread2(&SellTickts::seller_2, pThis);
thread1.detach();
thread2.detach();
}


main函数:



// ControlTest.cpp : 定义控制台应用程序的入口点。
//
void test();
#include "stdafx.h"
#include "SellTickts.h"
int _tmain(int argc, _TCHAR* argv[])
{
SellTickts sellTickets;
sellTickets.sell();//类函数作为函数入口

sell(&sellTickets);//友元函数作为函数入口

   thread t(test);//全局函数作为函数入口
t.detach();

thread t1(&SellTickets::seller_1,this);//类外部使用类函数作为函数入口
t1.detach();
) while (true) { } return 0; }


关于友元函数的分析 

分析:

1、类函数作为线程入口:thread thread1(&SellTickts::seller_1,this);

 

 第一个参数是函数的地址,这里有个概念:无论类都有多少个对象,这些对象所使用的类函数都是同一个地址,只有属性地址不同。



class A{
int temp;
void fun();
};


无论A类有多少个对象,这些对象所调用fun函数时,都是用的同一个地址,这个地址在类A定义的时候就有了;但是每个对象中的属性test地址都是不同的。那么fun函数如何区分当前是被哪个对象调用的呢?fun函数有一个默认参数this,当对象声明时,就有了一个对应的this,谁调用fun,就把谁的this传进去,这样就没问题了。

注意:如果想要类函数的地址,需要&类::函数,不能&对象::函数,这也是我刚开始不理解的地方。

第二个参数时this,是因为类函数都有默认参数this,所以这里必须传递参数this进去,否则编译出错。

 

 

2、友元函数作为线程入口:thread thread1(&SellTickts::seller_1, pThis);

 其实原理一样,不同的就是友元函数灭有默认this参数,需要程序员手动放入类指针。

 

 

3、使用全局函数作为线程入口:thread t(test);

不知道为啥这里不使用函数的地址

 

 

ps:怎么获取线程id和程序id

直接使用thread的get_id函数返回的是个thread类的内部类,不方便获取id,可以使用各平台自己的api



#ifndef __linux__
#include "windows.h"
#else
#include "unistd.h"
#endif

#ifndef __linux__
printf("now pid is %d \n", GetCurrentProcessId());
printf("now tid is %d \n", GetCurrentThreadId());

#else
printf("now pid is %d \n", getpid());
printf("now tid is %d \n", gettid());
#endif


 

 

ps:关于多线程调用同一全局函数的理解 

每个线程有自己的堆栈空间,而全局函数是只读的,当多个线程使用这个函数的时候,应该会把函数功能复制一份到自己的堆栈,然后函数内部的变量都放在自己的空间内,这样就互不影响了。

当然如果访问的是同一个全局变量,加互斥锁就可以了。

 

 

 

ps:thread::detach和join的区别

​join()​​操作是在​​std::thread t(func)​​后“某个”合适的地方调用,其作用是回收对应创建的线程的资源,避免造成资源的泄露。​​detach()​​操作是在​​std::thread t(func)​​后马上调用,用于把被创建的线程与做创建动作的线程分离,分离的线程变为后台线程,其后,创建的线程的“死活”就与其做创建动作的线程无关,它的资源会被init进程回收。

主要谈join:



void test()
{
}

bool do_other_things()
{
}

int main()
{
std::thread t(test);
int ret = do_other_things();
if(ret == ERROR) {
return -1;
}

t.join();
return 0;
}


可能执行不到join

解决:



class mythread {
private:
std::thread &m_t;

public:
explicit mythread(std::thread &t):m_t(t){}
~mythread() {
if(t.joinable()) {
t.join()
}
}

mythread(mythread const&) = delete;
mythread& operate=(mythread const&) = delete;
}

void test()
{
}

bool do_other_things()
{
}

int main()
{
std::thread t(test);
mythread q(t);

if(do_other_things()) {
return -1;
}

return 0;
}


 总结:

用detach,当前函数所在线程不会等待detack的子线程;

用join,当前函数所在线程会等待detack的子线程结束,再结束自己

 

ps:

1、休眠



#include <thread>
#include <chrono>


std::this_thread::sleep_for(std::chrono::milliseconds(50));


 2、获取线程id



cout<<std::this_thread::get_id();


 

 

二、std::async

1、原型



template<typename _Fn, typename... _Args>
future<typename result_of<_Fn(_Args...)>::type>
async(launch __policy, _Fn&& __fn, _Args&&... __args);

template<typename _Fn, typename... _Args>
future<typename result_of<_Fn(_Args...)>::type>
async(_Fn&& __fn, _Args&&... __args);


主要区别是是否有启动参数launch,定义如下:



enum class launch
{
async = 1,  
deferred = 2
};


  • std::launch::async:在调用async就开始创建线程。
  • std::launch::deferred:延迟加载方式创建线程。调用async时不创建线程,直到调用了future的get或者wait时才创建线程。

使用无参数的构造函数,默认启动策略是std::launch::async|std::launch::deferred,由系统决定何种测试,windows上实测是deferred。

2、启动



std::future<bool>   ret   =   std::async(&Store::_writeTagFile, this, dirPath);
std::future<bool> ret = std::async(std::launch::async, &Store::_writeTagFile, this, dirPath);


返回一个std::future 

 




长风破浪会有时,直挂云帆济沧海!