企业如何建官方网站,做门户网站怎么赚钱,成都网站运营公司,wordpress 页头设置C 11 之前#xff0c;C 语言没有对并发编程提供语言级别的支持#xff0c;这使得我们在编写可移植的并发程序时#xff0c;存在诸多的不便。现在 C 11 中增加了线程以及线程相关的类#xff0c;很方便地支持了并发编程#xff0c;使得编写的多线程程序的可移植性得到了很大… C 11 之前C 语言没有对并发编程提供语言级别的支持这使得我们在编写可移植的并发程序时存在诸多的不便。现在 C 11 中增加了线程以及线程相关的类很方便地支持了并发编程使得编写的多线程程序的可移植性得到了很大的提高。C 11 中提供的线程类叫做 std::thread基于这个类创建一个新的线程非常的简单只需要提供线程函数或者函数对象即可并且可以同时指定线程函数的参数。我们首先来了解一下这个类提供的一些常用 API1. 构造函数// ①
thread() noexcept;
// ②
thread( thread other ) noexcept;
// ③
template class function, class... args
explicit thread( Function f, Args... args );
// ④
thread( const thread ) delete;构造函数①默认构造函构造一个线程对象在这个线程中不执行任何处理动作构造函数②移动构造函数将 other 的线程所有权转移给新的 thread 对象。之后 other 不再表示执行线程。构造函数③创建线程对象并在该线程中执行函数 f 中的业务逻辑args 是要传递给函数 f 的参数任务函数 f 的可选类型有很多具体如下普通函数类成员函数匿名函数仿函数这些都是可调用对象类型可以是可调用对象包装器类型也可以是使用绑定器绑定之后得到的类型仿函数构造函数④使用 delete 显示删除拷贝构造不允许线程对象之间的拷贝2. 公共成员函数2.1 get_id()应用程序启动之后默认只有一个线程这个线程一般称之为主线程或父线程通过线程类创建出的线程一般称之为子线程每个被创建出的线程实例都对应一个线程 ID这个 ID 是唯一的可以通过这个 ID 来区分和识别各个已经存在的线程实例这个获取线程 ID 的函数叫做 get_id()函数原型如下std::thread::id get_id() const noexcept;示例程序如下#include
#include
#include
using namespace std;void func(int num, string str)
{for (int i 0; i 10; i){cout 子线程: i i num: num , str: str endl;}
}void func1()
{for (int i 0; i 10; i){cout 子线程: i i endl;}
}int main()
{cout 主线程的线程ID: this_thread::get_id() endl;thread t(func, 520, i love you);thread t1(func1);cout 线程t 的线程ID: t.get_id() endl;cout 线程t1的线程ID: t1.get_id() endl;
}thread t(func, 520, i love you);创建了子线程对象 tfunc() 函数会在这个子线程中运行func() 是一个回调函数线程启动之后就会执行这个任务函数程序猿只需要实现即可func() 的参数是通过 thread 的参数进行传递的520,i love you 都是调用 func() 需要的实参线程类的构造函数③ 是一个变参函数因此无需担心线程任务函数的参数个数问题任务函数 func() 一般返回值指定为 void因为子线程在调用这个函数的时候不会处理其返回值thread t1(func1);子线程对象 t1 中的任务函数func1()没有参数因此在线程构造函数中就无需指定了 通过线程对象调用 get_id() 就可以知道这个子线程的线程 ID 了t.get_id()t1.get_id()。基于命名空间 this_thread 得到当前线程的线程 ID在上面的示例程序中有一个 bug在主线程中依次创建出两个子线程打印两个子线程的线程 ID最后主线程执行完毕就退出了主线程就是执行 main () 函数的那个线程。默认情况下主线程销毁时会将与其关联的两个子线程也一并销毁但是这时有可能子线程中的任务还没有执行完毕最后也就得不到我们想要的结果了。当启动了一个线程创建了一个 thread 对象之后在这个线程结束的时候std::terminate ()我们如何去回收线程所使用的资源呢thread 库给我们两种选择加入式join()分离式detach()另外我们必须要在线程对象销毁之前在二者之间作出选择否则程序运行期间就会有 bug 产生。2.2 join()join() 字面意思是连接一个线程意味着主动地等待线程的终止线程阻塞。在某个线程中通过子线程对象调用 join() 函数调用这个函数的线程被阻塞但是子线程对象中的任务函数会继续执行当任务执行完毕之后 join() 会清理当前子线程中的相关资源然后返回同时调用该函数的线程解除阻塞继续向下执行。再次强调我们一定要搞清楚这个函数阻塞的是哪一个线程函数在哪个线程中被执行那么函数就阻塞哪个线程。该函数的函数原型如下void join();有了这样一个线程阻塞函数之后就可以解决在上面测试程序中的 bug 了如果要阻塞主线程的执行只需要在主线程中通过子线程对象调用这个方法即可当调用这个方法的子线程对象中的任务函数执行完毕之后主线程的阻塞也就随之解除了。修改之后的示例代码如下int main()
{cout 主线程的线程ID: this_thread::get_id() endl;thread t(func, 520, i love you);thread t1(func1);cout 线程t 的线程ID: t.get_id() endl;cout 线程t1的线程ID: t1.get_id() endl;t.join();t1.join();
}当主线程运行到第八行 t.join();根据子线程对象 t 的任务函数 func() 的执行情况主线程会做如下处理如果任务函数 func() 还没执行完毕主线程阻塞直到任务执行完毕主线程解除阻塞继续向下运行如果任务函数 func() 已经执行完毕主线程不会阻塞继续向下运行同样第 9 行的代码亦如此。为了更好的理解 join() 的使用再来给大家举一个例子场景如下程序中一共有三个线程其中两个子线程负责分段下载同一个文件下载完毕之后由主线程对这个文件进行下一步处理那么示例程序就应该这么写#include
#include
#include
using namespace std;void download1()
{// 模拟下载, 总共耗时500ms阻塞线程500msthis_thread::sleep_for(chrono::milliseconds(500));cout 子线程1: this_thread::get_id() , 找到历史正文.... endl;
}void download2()
{// 模拟下载, 总共耗时300ms阻塞线程300msthis_thread::sleep_for(chrono::milliseconds(300));cout 子线程2: this_thread::get_id() , 找到历史正文.... endl;
}void doSomething()
{cout 集齐历史正文, 呼叫罗宾.... endl;cout 历史正文解析中.... endl;cout 起航前往拉夫德尔.... endl;cout 找到OnePiece, 成为海贼王, 哈哈哈!!! endl;cout 若干年后草帽全员卒.... endl;cout 大海贼时代再次被开启.... endl;
}int main()
{thread t1(download1);thread t2(download2);// 阻塞主线程等待所有子线程任务执行完毕再继续向下执行t1.join();t2.join();doSomething();
}示例程序输出的结果子线程2: 72540, 找到历史正文....
子线程1: 79776, 找到历史正文....
集齐历史正文, 呼叫罗宾....
历史正文解析中....
起航前往拉夫德尔....
找到OnePiece, 成为海贼王, 哈哈哈!!!
若干年后草帽全员卒....
大海贼时代再次被开启....在上面示例程序中最核心的处理是在主线程调用 doSomething(); 之前在第 35、36行通过子线程对象调用了 join() 方法这样就能够保证两个子线程的任务都执行完毕了也就是文件内容已经全部下载完成主线程再对文件进行后续处理如果子线程的文件没有下载完毕主线程就去处理文件很显然从逻辑上讲是有问题的。声明本文于网络整理版权归原作者所有如来源信息有误或侵犯权益请联系我们删除或授权事宜。