学生为学校做网站,网站新闻 写法,做一个网站多少钱,做情网站C11之前#xff0c;C语言没有对并发编程提供语言级别的支持#xff0c;这使得我们在编写可移植的并发程序时#xff0c;存在诸多的不便。现在C11中增加了线程以及线程相关的类#xff0c;很方便地支持了并发编程#xff0c;使得编写的多线程程序的可移植性得到了很大的提高… C11之前C语言没有对并发编程提供语言级别的支持这使得我们在编写可移植的并发程序时存在诸多的不便。现在C11中增加了线程以及线程相关的类很方便地支持了并发编程使得编写的多线程程序的可移植性得到了很大的提高。 C11中提供的线程类叫做std::thread基于这个类创建一个新的线程非常的简单只需要提供线程函数或者函数对象即可并且可以同时指定线程函数的参数。我们首先来了解一下这个类提供的一些常用API
1. 构造函数
// ①
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 iostream
#include thread
#include chrono
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;
}
1.thread t(func, 520, i love you);创建了子线程对象tfunc()函数会在这个子线程中运行
func()是一个回调函数线程启动之后就会执行这个任务函数程序猿只需要实现即可func()的参数是通过thread的参数进行传递的520,i love you都是调用func()需要的实参线程类的构造函数③是一个变参函数因此无需担心线程任务函数的参数个数问题任务函数func()一般返回值指定为void因为子线程在调用这个函数的时候不会处理其返回值
2.thread t1(func1);子线程对象t1中的任务函数func1()没有参数因此在线程构造函数中就无需指定了
3.通过线程对象调用get_id()就可以知道这个子线程的线程ID了t.get_id()t1.get_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 iostream
#include thread
#include chrono
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()方法这样就能够保证两个子线程的任务都执行完毕了也就是文件内容已经全部下载完成主线程再对文件进行后续处理如果子线程的文件没有下载完毕主线程就去处理文件很显然从逻辑上讲是有问题的。
2.3 detach() detach()函数的作用是进行线程分离分离主线程和创建出的子线程。在线程分离之后主线程退出也会一并销毁创建出的所有子线程在主线程退出之前它可以脱离主线程继续独立的运行任务执行完毕之后这个子线程会自动释放自己占用的系统资源。其实就是孩子翅膀硬了和家里断绝关系自己外出闯荡了如果家里被诛九族还是会受牵连。该函数函数原型如下
void detach(); 线程分离函数没有参数也没有返回值只需要在线程成功之后通过线程对象调用该函数即可继续将上面的测试程序修改一下
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.detach();t1.detach();// 让主线程休眠, 等待子线程执行完毕this_thread::sleep_for(chrono::seconds(5));
} 注意事项线程分离函数detach()不会阻塞线程子线程和主线程分离之后在主线程中就不能再对这个子线程做任何控制了比如通过join()阻塞主线程等待子线程中的任务执行完毕或者调用get_id()获取子线程的线程ID。有利就有弊鱼和熊掌不可兼得建议使用join()。
2.4 joinable() joinable()函数用于判断主线程和子线程是否处理关联连接状态一般情况下二者之间的关系处于关联状态该函数返回一个布尔类型
返回值为true主线程和子线程之间有关联连接关系返回值为false主线程和子线程之间没有关联连接关系
bool joinable() const noexcept;
示例代码如下
#include iostream
#include thread
#include chrono
using namespace std;void foo()
{this_thread::sleep_for(std::chrono::seconds(1));
}int main()
{thread t;cout before starting, joinable: t.joinable() endl;t thread(foo);cout after starting, joinable: t.joinable() endl;t.join();cout after joining, joinable: t.joinable() endl;thread t1(foo);cout after starting, joinable: t1.joinable() endl;t1.detach();cout after detaching, joinable: t1.joinable() endl;
}
示例代码打印的结果如下
before starting, joinable: 0
after starting, joinable: 1
after joining, joinable: 0
after starting, joinable: 1
after detaching, joinable: 0
基于示例代码打印的结果可以得到以下结论
在创建的子线程对象的时候如果没有指定任务函数那么子线程不会启动主线程和这个子线程也不会进行连接在创建的子线程对象的时候如果指定了任务函数子线程启动并执行任务主线程和这个子线程自动连接成功子线程调用了detach()函数之后父子线程分离同时二者的连接断开调用joinable()返回false在子线程调用了join()函数子线程中的任务函数继续执行直到任务处理完毕这时join()会清理回收当前子线程的相关资源所以这个子线程和主线程的连接也就断开了因此调用join()之后再调用joinable()会返回false。
2.5 operator 线程中的资源是不能被复制的因此通过操作符进行赋值操作最终并不会得到两个完全相同的对象。
// move (1)
thread operator (thread other) noexcept;
// copy [deleted] (2)
thread operator (const other) delete;
通过以上操作符的重载声明可以得知
如果other是一个右值会进行资源所有权的转移如果other不是右值禁止拷贝该函数被显示删除delete不可用
3. 静态函数 thread线程类还提供了一个静态方法用于获取当前计算机的CPU核心数根据这个结果在程序中创建出数量相等的线程每个线程独自占有一个CPU核心这些线程就不用分时复用CPU时间片此时程序的并发效率是最高的。
static unsigned hardware_concurrency() noexcept;
示例代码如下
#include iostream
#include thread
using namespace std;int main()
{int num thread::hardware_concurrency();cout CPU number: num endl;
}
原文链接: https://subingwen.cn/cpp/thread/