C++多线程编程
一个进程指的是一个正在执行的应用程序,线程对应的英文名称为“thread”,它的功能是执行应用程序中的某个具体任务,比如一段程序、一个函数等。
每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间、文件资源等。一个进程至少包含 1 个线程,可以包含多个线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。
进程仅负责为各个线程提供所需的资源,真正执行任务的是线程,而不是进程。
图 1 进程和线程的关系如图所示,所有线程共享的进程资源有:
- 代码:即应用程序的代码;
- 数据:包括全局变量、函数内的静态变量、堆空间的数据等;
- 进程空间:操作系统分配给进程的内存空间;
- 打开的文件:各个线程打开的文件资源,也可以为所有线程所共享,例如线程 A 打开的文件允许线程 B 进行读写操作。
各个线程也可以拥有自己的私有资源,包括寄存器中存储的数据、线程执行所需的局部变量(函数参数)等
所谓多线程,即一个进程中拥有多(≥2)个线程,线程之间相互协作、共同执行一个应用程序
进程:进程可以理解为完成一件事的完整解决方案,而线程可以理解为这个解决方案中的的一个步骤,可能这个解决方案就这只有一个步骤,也可能这个解决方案有多个步骤。
线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,进程包含一个或者多个线程。
多线程:多线程是实现并发(并行)的手段,并发(并行)即多个线程同时执行,一般而言,多线程就是把执行一件事情的完整步骤拆分为多个子步骤,然后使得这多个步骤同时执行。
C++多线程:(简单情况下)C++多线程使用多个函数实现各自功能,然后将不同函数生成不同线程,并同时执行这些线程(不同线程可能存在一定程度的执行先后顺序,但总体上可以看做同时执行)。
进程与线程的区别
1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
4. 调度和切换:线程上下文切换比进程上下文切换要快得多
#include <stdio.h> #include <iostream.h> #include <assert.h> #include <pthread.h> //!bug1 不允许返回一个局部变量,static修饰后局部变量的生命周期持续整个程序运行阶段 /*void * Thread1(void *arg){ printf("http://c.biancheng.net\n"); char str[] = "Thread1成功执行"; return str; }*/ //!bug2 不允许,"Thread1成功执行"是const char[20],invalid conversion from ‘const void*’ to ‘void* /*void * Thread1(void *arg){ printf("http://c.biancheng.net\n"); return "Thread1成功执行"; }*/ //!bug3 testFunc.cpp:(.text+0x72):对‘pthread_create’未定义的引用 /*由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数: gcc testFunc.cpp -o thread.exe -lpthread(常用这种,-l添加静态库) 或者 gcc -o thread.exe -pthread testFunc.cpp*/ //!bug4 Assertion `res != 0' failed. /*res == 0时说明线程成功创建,assert(expection),expection为假时会输出错误*/ void * Thread1(void *arg){ printf("http://c.biancheng.net\n"); static char str[] = "Thread1成功执行"; return str; } void * Thread2(void *arg){ printf("C语言中文网\n"); static char str[] = "Thread2成功执行"; return str; } int main() { cout << "我是主线程"<<endl; int res; pthread_t mythread1,mythread2; void* thread_result; res = pthread_create(&mythread1, NULL, Thread1, NULL); assert (res == 0) ; res = pthread_create(&mythread2, NULL, Thread2, NULL); assert(res == 0); res = pthread_join(mythread1, &thread_result); //输出线程执行完毕后返回的数据 printf("%s\n", (char*)thread_result); res = pthread_join(mythread2, &thread_result); printf("%s\n", (char*)thread_result); printf("主线程执行完毕"); return 0; }
C++ 实现
#include <iostream> #include <thread> #include <mutex> using namespace std; /*编译命令g++ -o thread.exe testFunc.cpp -lpthread */ void proc(int a) { cout << "我是子线程,传入参数为" << a << endl; //* 命名空间std::this_thread提供了一组关于当前线程的函数 cout << "子线程中显示子线程id为" << this_thread::get_id() << endl; } int main() { cout << "我是主线程" << endl; int a = 9;
return 0; }
互斥锁:C++具有多线程并发特性,即多个线程同时执行,一个进程可以有多个线程,线程之间可以共享数据,当多个线程同时对共享资源进行操作时,会导致数据混乱
为避免共享资源混乱,C++提出互斥锁的概念:每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
通过“锁”就将资源的访问变成互斥操作,因为同一时刻,只能有一个线程持有该锁。
mutex一般用于为一段代码加锁,以保证这段代码的原子(atomic)操作(指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断),
可能被多个线程修改的数据,我们一般用互斥量(mutex)来保护。
#include <iostream> #include <thread> #include <mutex> using namespace std; mutex m;//实例化m对象,不要理解为定义变量 void proc1(int a){ m.lock(); cout << "proc1函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 2 << endl; m.unlock(); } void proc2(int a){ m.lock(); cout << "proc2函数正在改写a" << endl; cout << "原始a为" << a << endl; cout << "现在a为" << a + 1 << endl; m.unlock(); } int main(){ int a = 0; /* 在教程中把线程名与函数名重名了,导致编译不过,在编程中一定注意不要重名 thread proc1(proc1, a); thread proc2(proc2, a); */ thread th1(proc1,a); thread th2(proc2,a); th1.join(); th2.join(); return 0; }/*编译命令g++ -o thread.exe testFunc.cpp -lpthread */
我们期望的结果是
proc1函数正在改写a
原始a为0
现在a为2
proc2函数正在改写a
原始a为0
现在a为1
而如果不加入lock和unlock,两个线程可能会对a同时操作,最后出来的结果每次都不一样,如下:
hx@hx-Precision-3551:~/桌面/Test/src$ ./thread.exe
proc1函数正在改写aproc2函数正在改写a
原始a为0
现在a为2
原始a为0
现在a为1
hx@hx-Precision-3551:~/桌面/Test/src$ ./thread.exe
proc1函数正在改写a
原始a为0
proc2函数正在改写a
现在a为原始a为0
现在a为1
2
CMakeLists.txt
cmake_minimum_required (VERSION 2.8) project (demo) set(CMAKE_CXX_FLAGS "${CAMKE_CXX_FLAGS} -std=c++11") #设置可执行文件的输出目录,EXECUTABLE_OUT_PATH和PROJECT_SOURCE_DIR是CMake自带的预定义变量,其意义如下, #EXECUTABLE_OUTPUT_PATH :目标二进制可执行文件的存放位置 PROJECT_SOURCE_DIR:工程的根目录 #所以,这里set的意思是把存放可执行elf文件的位置设置为工程根目录下的bin目录。 set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.cpp) add_executable(main ${SRC_LIST}) #找到库 find_package(Threads REQUIRED) #把库文件与通过add_executable生成的目标文件连接 target_link_libraries(main ${CMAKE_THREAD_LIBS_INIT})


浙公网安备 33010602011771号