Tutorial Study Image

C++ 多线程


2023年6月15日, Learn eTutorial
791

多线程:它是什么?

多线程是指平台(操作系统、虚拟机等)或应用程序生成由多个执行线程(线程)组成的进程的能力。可由调度程序单独控制的最小指令组被称为执行线程。这些线程可以并行执行,从而提高程序效率。

在多核和多处理器系统中,多线程是指同时在多个核或处理器上执行多个线程。

在单核计算机中,多线程将时间分配给各个线程。操作系统从每个线程向 CPU 发送预定数量的指令。无法同时运行多个线程。操作系统只是模拟它们的并发执行。这种操作系统能力被称为多线程。

在 C++11 中,内置多线程功能可用。通过头文件 thread.h,可以实现多线程 C++ 程序的开发。

C++ 中多线程的特性

  • 多任务处理是我们的计算机同时执行两个或更多程序的能力,而多线程是多任务处理的一种特定类型。基于进程的多任务处理和基于线程的多任务处理是两种主要的多任务处理类型。
  • 并发程序执行由基于进程的多任务处理来处理。同一程序的各个部分的并发执行是基于线程的多任务处理的全部内容。
  • 多线程程序有两个或更多并发运行的组件。在此类程序中,每个组件都被称为一个线程,每个线程都指定一个唯一的执行路径。
  • 当某些任务的并发执行能更有效地利用系统资源时,就会使用多线程。
  • C++11 版本之前不原生支持多线程应用程序。操作系统完全负责提供此功能。

为什么要使用多线程?

多线程应用程序是指在应用程序本身内部同时运行多个线程的应用程序。

例如,如果我们想设计一个能够处理服务器所能处理的尽可能多的并发连接的服务器,如果我们为每个连接创建一个新线程,我们可以很快实现这一点。在某些情况下,多线程服务器会为每个传入连接启动一个新线程,而不是创建单独的套接字来处理它们,并且每个新线程也会创建一个新套接字。

GUI 应用程序中对多线程的需求是另一个常见的例子。GUI 应用程序在单个线程(主线程)中一次执行一个操作。此线程要么正在处理事件,要么正在等待事件。因此,当用户启动耗时活动时,用户界面将冻结。在这种情况下需要使用多线程。

通过创建属于 Thread 子类的对象,主线程可以启动新线程。当应用程序是多线程时,GUI 在其自己的线程中运行,而其他处理在其他线程中进行。这确保了即使在处理繁忙时,应用程序的 GUI 也能保持响应。

我们如何创建线程?

首先,请确保您的程序包含线程头文件。

语法


#include <thread>
 

要创建线程,您必须首先构造一个线程类的对象。


// This thread is not a representation of any execution thread.
thread t_empty;
 

如您所见,当使用线程类的默认构造函数时,我们不会向线程提供任何数据。这表明在此线程中,没有执行任何操作。需要启动一个线程。有多种方法可以实现。

创建线程时,可以将函数指针传递给线程的函数构造函数。线程一经创建,此函数就开始在该线程中工作。请看以下示例:


#include <iostream>
#include <thread> 
using namespace std;
void threadFunc()
{
  cout << "Welcome all to Multithreading" << endl;
}
int main()
{
  //pass a function to the thread
  thread funcTest1(threadFunc);
}

 

即使编译时没有任何问题,您也会遇到运行时问题。

您可以看到主线程将 funcTest1 作为新线程,参数为 threadFunc。主线程不等待 funcTest1 完成。它继续工作。即使主线程已完成运行,funcTest1 仍在活动。这会导致错误。在主线程终止之前,所有其他线程也必须终止。

连接线程

线程连接通过使用线程类的 join() 成员函数实现。

语法


void join();
 

此方法仅在所有线程都已终止后才返回。换句话说,主线程将等待子线程完成其执行。

终止线程

要终止 POSIX 线程,请使用下面列出的方法。


#include <pthread.h>
pthread_exit (status) 

此处,使用 pthread exit 有意终止线程。当线程已完成其任务且不再需要时,通常会调用 pthread exit() 例程。

即使 main() 在它创建的所有线程完成之前就用 pthread exit() 退出了,其他线程仍将继续运行。如果不是,它们将在 main() 完成后自动终止。

示例

使用 pthread create() 函数,这个简单的示例代码创建了 5 个线程。每个线程在打印消息“Hello World!”后通过调用 pthread_exit() 终止。

使用 pthread create() 函数的示例程序


#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

void *PrintHello(void *threadid) {
   long tid;
   tid = (long)threadid;
   cout << "Hello World! Thread ID, " << tid << endl;
   pthread_exit(NULL);
}

int main () {
   pthread_t threads[NUM_THREADS];
   int rc;
   int i;
   
   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

 

输出


main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4

上述程序应使用 -lpthread 库构建。$gcc test.cpp -lpthread

现在执行您的程序,它将产生上面列出的结果。

向线程传递参数

借助结构,此示例演示了如何传递多个参数。线程回调接受任何数据类型,因为它指向 void,如下例所示。

向线程传递参数的示例程序


#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

struct thread_data {
   int  thread_id;
   char *message;
};

void *PrintHello(void *threadarg) {
   struct thread_data *my_data;
   my_data = (struct thread_data *) threadarg;

   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;

   pthread_exit(NULL);
}

int main () {
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = "This is message";
      rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

 

输出


main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 2 Message : This is message
Thread ID : 0 Message : This is message
Thread ID : 1 Message : This is message
Thread ID : 4 Message : This is message

连接和分离线程

可以使用以下两种过程来附加或分离线程


pthread_join (threadid, status) 
pthread_detach (threadid) 
 

pthread join() 方法会暂停调用线程,直到给定的 'threadid' 线程终止/结束。线程是在创建时由其属性之一确定是可连接的还是分离的。线程只有在创建为可连接线程时才能连接。已分离的线程在创建后永远不能连接。

以下示例使用 Pthread join 方法演示了如何等待线程完成。

 


#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS 5

void *wait(void *t) {
   int i;
   long tid;

   tid = (long)t;

   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}

int main () {
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;

   // Initialize and set thread joinable
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], &attr, wait, (void *)i );
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }

   // free attribute and wait for the other threads
   pthread_attr_destroy(&attr);
   for( i = 0; i < NUM_THREADS; i++ ) {
      rc = pthread_join(threads[i], &status);
      if (rc) {
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }

   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

输出


main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Thread with id : 0 .... exiting
Sleeping in thread
Thread with id : 1 .... exiting
Sleeping in thread
Thread with id : 2 .... exiting
Sleeping in thread
Thread with id : 3 .... exiting
Sleeping in thread
Thread with id : 4 .... exiting
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.

多线程与多进程

多进程

进程是资源分配和保护单元。某些资源,包括虚拟内存、I/O 处理器和信号处理器,由进程管理。使用 MMU,进程受到其他进程的保护。缺点:进程间通信 (IPC) 可能很困难且效率低下。

多线程

在进程内操作的计算单元被称为线程。线程负责管理各种资源,包括堆栈、寄存器、信号掩码、优先级和线程特定数据。优点:进程之间的 IPC 效率低于线程之间的 IPC。缺点:线程之间可能会冲突。