C++并发之探索编程一
创始人
2024-05-30 01:47:49
0

文章目录

  • “你最熟悉的hello world”
  • 线程管控(等待、分离、传递参数、移交线程归属权)
    • 线程分离
    • 向线程函数传递参数
    • 移交线程归属权
    • 在运行时选择线程数量
    • 识别线程

“你最熟悉的hello world”

在一个进程或者线程里面输出 "hello world"是怎么做的呢?

#include 
int main()
{std::cout << "Hello World!\n";
}

在该线程中起始函数为main函数,当输出完"hello world",该线程便终止了。

如果我们想另外开辟一个线程输出"hello world"该如何操作呢?在C++11中便已经封装好了管控线程的函数。
我们可以引入头文件,来启动线程。

#include 
#include 
void hello() {std::cout << "hello world" << std::endl;
}
int main()
{std::cout << "I want start a new thread to print hello world." << std::endl;//创建子线程并传递入口函数std::thread t(hello);//调用join函数,阻塞当前主线程,直到子线程运行结束t.join();return 0;
}

在本例子中 t 作为 std::thread对象将函数hello()作为新线程的入口函数。std::thread对象的地址C++运行时系统动态分配的,通常在创建线程时自动分配。该地址指向新线程的控制结构,它包含线程的一些信息,比如线程id,状态,堆栈指针等等。我们看到 hello 作为一个参数传入到线程构造函数中,该参数通常是一个可调用对象(函数指针,lambda表达式或函数对象),'std::thread’对象将可调用对象的地址保存到新线程的控制结构里面,以便在运行的时候调用。

而’std::thread::join()'是一个成员函数,它用于等待该线程对象相关联的线程运行结束,调用 ‘join()’ 函数会阻塞当前线程的运行,直到该线程对象相关联的线程执行完成为止。
使用 ‘std::thread::join’ 的目的就是为了让主线程等待所有的子线程运行结束后再退出。
注意:一个线程对象只能调用一次 'std::thread::join()'函数,否则会触发 'std::system_error’异常。

线程管控(等待、分离、传递参数、移交线程归属权)

线程分离

上面讲到了 ‘std::thread::join()’ 函数阻塞主线程等待子线程执行结束,那如果用户不想等待呢?当然也有办法,那就是使用 ‘std::thread::detach()’ 函数使线程对象与其管理的执行线程相分离,将一个线程对象与其执行线程分离后,这个线程对象就不再与其所管理的执行线程相关联,它的执行状态将与执行线程独立。

这意味着线程的执行将会在后台继续进行,即使线程对象被销毁了,执行线程仍然可以继续执行。但我们在使用这个函数的时候,需要检验一下线程对象是否可连接(即线程对象和相关联的执行线程是否绑定),如果可连接,那么可以执行分离。但是如果不可连接,依然调用’std::thread::detach()',将导致程序终止。

线程对象在分离之后将无法重新连接到执行线程,因此需要确保不需要再次连接到执行线程。被分离出去的线程又可被称为守护线程(daemon thread)

#include 
#include void hello() {std::cout << "hello world" << std::endl;
}
int main()
{std::cout << "I want start a new thread to print hello world." << std::endl;std::thread t(hello);if (t.joinable()) {t.detach();std::cout << "Thread is joinable\n";}else {std::cout << "Thread is not joinable\n";}std::cout << "Exiting main function\n";return 0;
}

注意: 当线程分离时,对于可调用对象中含有指针或引用,一定要谨慎操作,如果指针或引用指向了一个主线程的一个已摧毁变量,程序就会出问题。解决方法:可令线程完全自含,将数据复制到新线程的内部,而不是共享数据。

向线程函数传递参数

我们已经学会了创建线程、等待子线程、分离子线程,那么当我们新创建的线程需要参数运行时,我们该如何想向线程函数中传递参数呢?有如下几种方法可以传递。

  • 使用std::thread的构造函数
    std::thread类提供了多个构造函数,其中一个构造函数可以接受一个可调用对象和多个参数,可以使用这个构造函数来向线程中传递参数。
#include 
#include void func(int arg1, int arg2)
{std::cout << "arg1 = " << arg1 << ", arg2 = " << arg2 << std::endl;
}int main()
{int arg1 = 10;int arg2 = 20;std::thread t(func, arg1, arg2);t.join();return 0;
}

在这个示例中,调用 std::thread 的构造函数时,除了要指定线程函数 func 外,还将 arg1 和 arg2 作为构造函数的参数传递给了该线程。这样,在创建新线程时,这两个参数的值会被复制到新线程的私有存储空间中。因此,在新线程中修改这两个参数的值不会影响主线程中这两个参数的值,也不会影响其他线程对这两个参数的访问。

由于这里使用的是值传递,所以可以避免线程之间的竞争和共享数据的问题。但也需要注意,如果参数的拷贝开销很大,可能会影响程序的效率。如果需要在多个线程之间共享数据,则需要使用其他的线程通信机制来保证数据的同步和一致性。

  • 那么如果我想要引用传递,在新线程中更改参数也会影响到主线程中的参数呢?可以使用这样的方式:可以使用到 std::ref() 函数
#include 
#include void func(int& a, int& b) {a++;b++;std::cout << "a is " << a << " , b is " << b << std::endl;
}int main() {int argc1 = 10;int argc2 = 20;std::thread t(func, std::ref(argc1), std::ref(argc2));t.join();std::cout << "main thread argc1 is " << argc1 << " , argc2 is " << argc2 << std::endl;return 0;
}
  • 使用Lambda表达式
    Lambda表达式是C++11引入的一个新特性,可以用于创建匿名函数对象。使用Lambda表达式可以方便地向线程中传递参数。
#include 
#include int main()
{int arg1 = 10;int arg2 = 20;std::thread t([&arg1, &arg2]() {std::cout << "arg1 = " << arg1 << ", arg2 = " << arg2 << std::endl;});t.join();return 0;
}

在上述示例代码中,使用Lambda表达式向线程t中传递参数arg1和arg2。
需要注意的是,如果要向线程中传递引用类型的参数,需要使用std::ref()函数将参数包装为一个引用包装器。例如,可以使用std::thread t(func, std::ref(arg1), std::ref(arg2))向线程中传递引用类型的参数。

  • 传递成员函数
    要在 std::thread 中使用成员函数,需要将成员函数作为线程函数,并将类对象的指针作为参数传递给 std::thread 的构造函数。
#include 
#include 
#include class MyClass
{
public:void func(int arg){std::cout << "arg = " << arg << std::endl;}
};int main()
{MyClass myObj;int arg = 10;std::thread t(&MyClass::func, &myObj, arg);t.join();return 0;
}

在这个示例中,通过&MyClass::func获取函数的地址,并将对象指针&myObj 和参数arg传递给了std::thread的构造函数中,由于成员函数func()需要类对象的成员变量和其他成员函数,所以需要将类对象的指针作为第一个参数传递给线程函数。

  • 参数移动而不能复制的传递参数
    使用 std::move 来传递只能移动但不能复制的对象。这种方式适用于需要将对象的所有权转移给新线程的情况,可以避免在多线程环境下进行复制或共享对象的操作。
#include 
#include 
#include void func(std::string&& str)
{std::cout << "str = " << str << std::endl;
}int main()
{std::string str = "hello";std::thread t(func, std::move(str));t.join();return 0;
}

在这个示例中,将字符串 str 作为右值引用传递给线程函数 func。由于右值引用表示的是对象的所有权,因此在传递参数时需要使用 std::move 来将对象的所有权转移给新线程。
需要注意的是,一旦对象的所有权被转移,原始的对象将不再可用,因此在使用 std::move 传递参数时需要小心。此外,如果多个线程同时访问同一个对象,可能会导致竞争条件的发生,需要采取适当的同步措施来避免这个问题。

移交线程归属权

  • 首先可以使用std::thread::swap()函数进行交换线程所有权
#include 
#include void my_thread(int n) {std::cout << "Thread " << n << " is running." << std::endl;
}int main() {std::thread t1(my_thread, 1);std::thread t2;// 交换两个线程的内部状态,使得t2获得了t1的执行函数和其他内部状态t1.swap(t2);// 等待t2执行完成t2.join();std::cout << "Thread 1 has been moved to t2." << std::endl;return 0;
}
  • 还有一种方法可以移交线程所有权,就是使用std::move()
#include 
#include void func()
{std::cout << "Thread running" << std::endl;
}int main()
{std::thread t(func);// 移交线程所有权给t2std::thread t2(std::move(t));t2.join();return 0;
}

在运行时选择线程数量

我们已经学会了创建线程、传递线程参数、转移线程所有权,那么我们在写程序的时候该开多少线程合适呢?我们可以通过std::thread::hardware_concurrency()函数,返回一个 unsigned int 值,表示当前系统支持的并发线程数。这个值并不是一个精确的硬件限制,而是一个估计值,取决于当前系统的硬件和其他运行时因素。通常情况下,这个值与 CPU 的核心数有关,但是在某些情况下,它可能会受到其他因素的影响,例如 CPU 频率、内存带宽、I/O 带宽等。

在编写多线程程序时,使用 std::thread::hardware_concurrency() 函数可以帮助我们根据系统能力来确定线程数量,从而最大化系统资源的利用率。例如,以下代码片段使用 std::thread::hardware_concurrency() 函数来确定系统支持的最大线程数量:

#include 
#include int main()
{unsigned int n = std::thread::hardware_concurrency();std::cout << "This system can run " << n << " concurrent threads." << std::endl;return 0;
}

识别线程

我们可以使用 std::this_thread::get_id() 函数来获取当前线程的 ID,这个 ID 是一个唯一的整数,表示当前线程的身份。例如,以下代码片段演示了如何获取当前线程的 ID 并输出它的值:

#include 
#include void func()
{std::cout << "Thread ID = " << std::this_thread::get_id() << std::endl;
}int main()
{std::thread t(func);std::cout << "Main thread ID = " << std::this_thread::get_id() << std::endl;t.join();return 0;
}

需要注意的是,如果在单线程程序中调用 std::this_thread::get_id(),它将返回一个默认的 ID,但是这个 ID 并不代表任何真实的线程。所以,这个函数只能用于多线程程序中。

std::thread::id 是一个类型,表示线程的唯一标识符。每个线程都有一个不同的 std::thread::id,可以通过 std::thread::get_id() 函数获取。

#include 
#include void func()
{std::cout << "Thread ID = " << std::this_thread::get_id() << std::endl;
}int main()
{std::thread t1(func);std::thread t2(func);if (t1.get_id() == t2.get_id()) {std::cout << "Thread IDs are the same." << std::endl;} else {std::cout << "Thread IDs are different." << std::endl;}std::cout << "t1 ID = " << t1.get_id() << std::endl;std::cout << "t2 ID = " << t2.get_id() << std::endl;t1.join();t2.join();return 0;
}

上述代码创建了两个新线程 t1 和 t2,然后通过 std::thread::get_id() 函数获取它们的 std::thread::id。接着,代码比较了这两个 ID 是否相同,以及打印了每个线程的 ID。最后,通过调用 std::thread::join() 函数等待线程结束。
如果知道线程对象名称就可以使用std::thread::get_id()来获取 std::thread::id, 如果想在该线程中不用知道线程绑定对象名称就获取 std::thread::id ,可以使用std::this_thread::get_id() 来获取。

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...