线程
线程
1.线程概述
线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源,可以这样理解,进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。
先从概念上了解一下线程和进程之间的区别:
-
进程有自己独立的地址空间,多个线程共用同一个地址空间
-
线程更加节省系统资源,效率不仅可以保持的,而且能够更高
-
在一个地址空间中多个线程独享:每个线程都有属于自己的栈区,寄存器(内核中管理的)
-
在一个地址空间中多个线程共享: 代码段,堆区,全局数据区,打开的文件(文件描述符表)都是线程共享的
-
-
线程是程序的最小执行单位, 进程是操作系统中最小的资源分配单位
-
每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片
-
一个地址空间中可以划分出多个线程,在有效的资源基础上,能够抢更多的CPU时间片

-
-
CPU的调度和切换:线程的上下文切换比进程要快的多
上下文切换:进程/线程分时复用CPU时间片,在切换之前会将上一个任务的状态进行保存,下次切换回这个任务的时候,加载这个状态继续运行,任务从保存到再次加载这个过程就是一次上下文切换。
-
线程更加廉价,启动速度更快,退出也快,对系统资源的冲击小。
在处理多任务程序的时候使用多线程比使用多进程要更有优势,但是线程并不是越多越好,如何控制线程的个数呢?
-
文件IO操作:文件IO对CPU是使用率不高,因此可以分时复用CPU时间片, 线程的个数 = 2 * CPU核心数 (效率最高)
-
处理复杂的算法(主要是CPU进行运算, 压力大),线程的个数 = CPU的核心数 (效率最高)
2.创建线程
2.1 线程函数
每一个线程都有一个唯一的线程ID,ID类型为pthread_t,这个ID是一个无符号长整形数,如果想要得到当前线程的线程ID,可以调用如下函数:
pthread_t pthread_self(void); // 返回当前线程的线程ID
在一个进程中调用线程创建函数,就可得到一个子线程,和进程不同,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
// Compile and link with -pthread, 线程库的名字叫pthread, 全名: libpthread.so libptread.a
-
参数:
-
thread:传出参数,是无符号长整形数,线程创建成功,会将线程ID写入到这个指针指向的内存中
-
attr:线程的属性,一般情况下使用默认属性即可,写NULL
-
start_routine:函数指针,创建出的子线程的处理动作,也就是该函数在子线程中执行。
-
arg:作为实参传递到 start_routine 指针指向的函数内部
-
-
返回值:线程创建成功返回0,创建失败返回对应的错误号
2.2 创建线程
下面是创建线程的示例代码,在创建过程中一定要保证编写的线程函数与规定的函数指针类型一致:void (start_routine) (void *):
//pthread_create.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* working(void* arg)
{
printf("我是子线程,线程ID:%ld\n", pthread_self());
for(int i = 0; i < 9; i++)
{
printf("子线程i = %d\n", i);
}
return NULL;
}
int main()
{
//1.创建一个子线程
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("子线程创建成功,线程ID:%ld\n", tid);
//2.子线程不会执行下面的代码,主线程执行
printf("我是主线程,线程ID:%ld\n", pthread_self());
for(int i = 0; i < 3; i++)
{
printf("主线程:i = %d\n", i);
}
return 0;
}
编译测试程序,会看到如下错误信息:
$ gcc pthread_create.c
/tmp/cctkubA6.o: In function `main':
pthread_create.c:(.text+0x7f): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
错误原因是因为编译器链接不到线程库文件(动态库),需要在编译的时候通过参数指定出来,动态库名为 libpthread.so需要使用的参数为 -l,根据规则掐头去尾最终形态应该写成:-lpthread(参数和参数值中间可以有空格)。正确的编译命令为:
# pthread_create 函数的定义在某一个库中, 编译的时候需要加库名 pthread
cmt@cmt-virtual-machine:~/LinuxStudy$ gcc pthread_create.c -lpthread -o app
cmt@cmt-virtual-machine:~/LinuxStudy$ ./app
主线程:i = 0
主线程:i = 1
主线程:i = 2
主线程创建成功,线程ID:140358836086528
cmt@cmt-virtual-machine:~/LinuxStudy$
在打印的日志输出中为什么子线程处理函数没有执行完毕呢(只看到了子线程的部分日志输出)?
主线程一直在运行,执行期间创建出了子线程,说明主线程有CPU时间片, 在这个时间片内将代码执行完毕了,主线程就退出了。子线程被创建出来之后需要抢cpu时间片,抢不到就不能运行,如果主线程退出了, 虚拟地址空间就被释放了,子线程就一并被销毁了。但是如果某一个子线程退出了,主线程仍在运行,虚拟地址空间依旧存在。得到的结论:在没有人为干预的情况下,虚拟地址空间的生命周期和主线程是一样的,与子线程无关。
目前的解决方案: 让子线程执行完毕,主线程再退出,可以在主线程中添加挂起函数 sleep();
//pthread_create.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* working(void* arg)
{
for(int i = 0; i < 9; i++)
{
printf("子线程i = %d\n", i);
}
printf("我是子线程,线程ID:%ld\n", pthread_self());
return NULL;
}
int main()
{
//1.创建一个子线程
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
//2.子线程不会执行下面的代码,主线程执行
for(int i = 0; i < 3; i++)
{
printf("主线程:i = %d\n", i);
}
printf("主线程创建成功,线程ID:%ld\n", tid);
sleep(3);
return 0;
}
cmt@cmt-virtual-machine:~/LinuxStudy$ gcc pthread_create.c -lpthread -o app
cmt@cmt-virtual-machine:~/LinuxStudy$ ./app
主线程:i = 0
主线程:i = 1
主线程:i = 2
主线程创建成功,线程ID:140588316739328
子线程i = 0
子线程i = 1
子线程i = 2
子线程i = 3
子线程i = 4
子线程i = 5
子线程i = 6
子线程i = 7
子线程i = 8
我是子线程,线程ID:140588316739328
3.线程退出
在编写多线程程序的时候,如果想要让线程退出,但是不希望导致虚拟地址空间的释放(针对于主线程),我们就可以调用线程库中的线程退出函数,只要调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行,不管是在子线程或者主线程中都可以使用。
#include <pthread.h>
void pthread_exit(void *retval);
- 参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为NULL
下面是线程退出的示例代码,可以在任意线程的需要的位置调用该函数:
//pthread_create.c
// 标准输入输出头文件,用于printf等打印函数
#include <stdio.h>
// 标准库头文件,用于内存分配、程序退出等函数
#include <stdlib.h>
// Unix标准头文件,提供sleep、getpid等系统调用
#include <unistd.h>
// 字符串处理头文件,本示例未用到,保留原引用
#include <string.h>
// POSIX线程库头文件,必须包含才能使用pthread相关函数
#include <pthread.h>
/**
* @brief 子线程的执行函数
* @param arg 传递给子线程的参数(本示例未使用)
* @return void* 线程退出时的返回值(本示例返回NULL)
*/
void* working(void* arg)
{
// 子线程循环打印9次计数
for(int i = 0; i < 9; i++)
{
printf("子线程i = %d\n", i);
}
// 获取并打印子线程自身的ID(pthread_t类型本质是无符号长整型)
printf("我是子线程,线程ID:%ld\n", (unsigned long)pthread_self());
// 子线程执行完毕,返回NULL
return NULL;
}
int main()
{
// 1. 创建一个子线程
// pthread_create函数原型:
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
// 参数说明:
// - thread:输出参数,用于存储创建成功的子线程ID
// - attr:线程属性,NULL表示使用默认属性
// - start_routine:子线程的入口函数(函数指针)
// - arg:传递给子线程入口函数的参数(本示例传NULL)
// 定义变量存储子线程ID
pthread_t tid;
// 调用pthread_create创建子线程,参数依次为:子线程ID地址、默认属性、子线程函数、无参数
int ret = pthread_create(&tid, NULL, working, NULL);
// 检查线程创建是否成功(可选:增强代码健壮性)
if(ret != 0)
{
char* err_msg = strerror(ret);
printf("线程创建失败:%s\n", err_msg);
return -1;
}
// 打印主线程ID
printf("我是主线程,线程ID:%ld\n", (unsigned long)pthread_self());
// 让主线程退出(仅主线程终止,不影响子线程执行)
// 如果用return 0,主线程退出会导致整个进程终止,子线程可能未执行完就被终止
pthread_exit(NULL);
// 执行到pthread_exit后,以下代码不会被执行
return 0;
}
cmt@cmt-virtual-machine:~/LinuxStudy$ gcc pthread_create.c -lpthread -o app
cmt@cmt-virtual-machine:~/LinuxStudy$ ./app
主线程创建成功,线程ID:139792145000192
子线程i = 0
子线程i = 1
子线程i = 2
子线程i = 3
子线程i = 4
子线程i = 5
子线程i = 6
子线程i = 7
子线程i = 8
我是子线程,线程ID:139792145000192
4.线程回收
4.1 线程函数
线程和进程一样,子线程退出的时候其内核资源主要由主线程回收,线程库中提供的线程回收函叫做pthread_join(),这个函数是一个阻塞函数,如果还有子线程在运行,调用该函数就会阻塞,子线程退出函数解除阻塞进行资源的回收,函数被调用一次,只能回收一个子线程,如果有多个子线程则需要循环进行回收。
另外通过线程回收函数还可以获取到子线程退出时传递出来的数据,函数原型如下:
#include <pthread.h>
// 这是一个阻塞函数, 子线程在运行这个函数就阻塞
// 子线程退出, 函数解除阻塞, 回收对应的子线程资源, 类似于回收进程使用的函数 wait()
int pthread_join(pthread_t thread, void **retval);
-
参数:
-
thread:要被回收的子线程的线程ID
-
retval:二级指针,指向一级指针的地址,是一个传出参数,这个地址中存储了pthread_exit() 传递出的数据,如果不需要这个参数,可以指定为NULL
-
-
返回值:线程回收成功返回0,回收失败返回错误号。
4.2 回收子线程数据
在子线程退出的时候可以使用pthread_exit()的参数将数据传出,在回收这个子线程的时候可以通过phread_join()的第二个参数来接收子线程传递出的数据。接收数据有很多种处理方式,下面来列举几种:
4.2.1 使用子线程栈
通过函数pthread_exit(void *retval);可以得知,子线程退出的时候,需要将数据记录到一块内存中,通过参数传出的是存储数据的内存的地址,而不是具体数据,由因为参数是void*类型,所有这个万能指针可以指向任意类型的内存地址。先来看第一种方式,将子线程退出数据保存在子线程自己的栈区:
//pthread_create.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
struct Test
{
int num;
int age;
};
void* working(void* arg)
{
printf("我是子线程,线程ID:%ld\n", (unsigned long)pthread_self());
for(int i = 0; i < 10; i++)
{
printf("子线程,i = %d\n", i);
}
struct Test t;
t.num = 100;
t.age = 6;
pthread_exit(&t);
return NULL;
}
int main()
{
//1.创建子线程
// int pthread_create(pthread_t *thread, const pthreadA_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t tid;
int ret = pthread_create(&tid, NULL, working, NULL);
printf("子线程创建成功,子线程ID:%ld\n", tid);
printf("我是主线程,线程ID:%ld\n", (unsigned long)pthread_self());
for(int i = 0; i < 3; i++)
{
printf("主线程,i = %d\n", i);
}
//阻塞等待子线程退出
void* ptr = NULL;
pthread_join(tid, &ptr);
//打印信息
struct Test* tt = (struct Test*)ptr;
printf("num=%d, age=%d\n", tt->num, tt->age);
printf("子线程资源成功被回收!\n");
return 0;
}
编译并执行测试程序:
cmt@cmt-virtual-machine:~/LinuxStudy$ gcc pthread_create.c -lpthread -o app
cmt@cmt-virtual-machine:~/LinuxStudy$ ./app
子线程创建成功,子线程ID:140187820369664
我是主线程,线程ID:140187820373824
主线程,i = 0
主线程,i = 1
主线程,i = 2
我是子线程,线程ID:140187820369664
子线程,i = 0
子线程,i = 1
子线程,i = 2
子线程,i = 3
子线程,i = 4
子线程,i = 5
子线程,i = 6
子线程,i = 7
子线程,i = 8
子线程,i = 9
num=87828224, age=32640
子线程资源成功被回收!
cmt@cmt-virtual-machine:~/LinuxStudy$
通过打印的日志可以发现,在主线程中没有没有得到子线程返回的数据信息,具体原因是这样的:
如果多个线程共用同一个虚拟地址空间,每个线程在栈区都有一块属于自己的内存,相当于栈区被这几个线程平分了,当线程退出,线程在栈区的内存也就被回收了,因此随着子线程的退出,写入到栈区的数据也就被释放了。
4.2.2 使用全局变量
位于同一虚拟地址空间中的线程,虽然不能共享栈区数据,但是可以共享全局数据区和堆区数据,因此在子线程退出的时候可以将传出数据存储到全局变量、静态变量或者堆内存中。在下面的例子中将数据存储到了全局变量中:
//pthread_create.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
struct Test
{
int num;
int age;
};
struct Test t;
void* working(void* arg)
{
printf("我是子线程,线程ID:%ld\n", (unsigned long)pthread_self());
for(int i = 0; i < 10; i++)
{
printf("子线程,i = %d\n", i);
}
t.num = 100;
t.age = 6;
pthread_exit(&t);
return NULL;
}
int main()
{
//1.创建子线程
// int pthread_create(pthread_t *thread, const pthreadA_attr_t *attr,
// oid *(*start_routine) (void *), void *arg);
pthread_t tid;
int ret = pthread_create(&tid, NULL, working, NULL);
printf("子线程创建成功,子线程ID:%ld\n", tid);
printf("我是主线程,线程ID:%ld\n", (unsigned long)pthread_self());
for(int i = 0; i < 3; i++)
{
printf("主线程,i = %d\n", i);
}
//阻塞等待子线程退出
void* ptr = NULL;
pthread_join(tid, &ptr);
//打印信息
struct Test* tt = (struct Test*)ptr;
printf("num=%d, age=%d\n", tt->num, tt->age);
printf("子线程资源成功被回收!\n");
return 0;
}
cmt@cmt-virtual-machine:~/LinuxStudy$ gcc pthread_create.c -lpthread -o app
cmt@cmt-virtual-machine:~/LinuxStudy$ ./app
子线程创建成功,子线程ID:139698033227520
我是主线程,线程ID:139698033231680
主线程,i = 0
主线程,i = 1
主线程,i = 2
我是子线程,线程ID:139698033227520
子线程,i = 0
子线程,i = 1
子线程,i = 2
子线程,i = 3
子线程,i = 4
子线程,i = 5
子线程,i = 6
子线程,i = 7
子线程,i = 8
子线程,i = 9
num=100, age=6
子线程资源成功被回收!
4.2.3 使用主线程栈
虽然每个线程都有属于自己的栈区空间,但是位于同一个地址空间的多个线程是可以相互访问对方的栈空间上的数据的。由于很多情况下还需要在主线程中回收子线程资源,所以主线程一般都是最后退出,基于这个原因在下面的程序中将子线程返回的数据保存到了主线程的栈区内存中:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 定义结构
struct Persion
{
int id;
char name[36];
int age;
};
// 子线程的处理代码
void* working(void* arg)
{
struct Persion* p = (struct Persion*)arg;
printf("我是子线程, 线程ID: %ld\n", pthread_self());
for(int i=0; i<9; ++i)
{
printf("child == i: = %d\n", i);
if(i == 6)
{
// 使用主线程的栈内存
p->age =12;
strcpy(p->name, "tom");
p->id = 100;
// 该函数的参数将这个地址传递给了主线程的pthread_join()
pthread_exit(p);
}
}
return NULL;
}
int main()
{
// 1. 创建一个子线程
pthread_t tid;
struct Persion p;
// 主线程的栈内存传递给子线程
pthread_create(&tid, NULL, working, &p);
printf("子线程创建成功, 线程ID: %ld\n", tid);
// 2. 子线程不会执行下边的代码, 主线程执行
printf("我是主线程, 线程ID: %ld\n", pthread_self());
for(int i=0; i<3; ++i)
{
printf("i = %d\n", i);
}
// 阻塞等待子线程退出
void* ptr = NULL;
// ptr是一个传出参数, 在函数内部让这个指针指向一块有效内存
// 这个内存地址就是pthread_exit() 参数指向的内存
pthread_join(tid, &ptr);
// 打印信息
printf("name: %s, age: %d, id: %d\n", p.name, p.age, p.id);
printf("子线程资源被成功回收...\n");
return 0;
}
在上面的程序中,调用pthread_create()创建子线程,并将主线程中栈空间变量p的地址传递到了子线程中,在子线程中将要传递出的数据写入到了这块内存中。也就是说在程序的main()函数中,通过指针变量ptr或者通过结构体变量p都可以读出子线程传出的数据。
5.线程分离
在某些情况下,程序中的主线程有属于自己的业务处理流程,如果让主线程负责子线程的资源回收,调用pthread_join()只要子线程不退出主线程就会一直被阻塞,主要线程的任务也就不能被执行了。
在线程库函数中为我们提供了线程分离函数pthread_detach(),调用这个函数之后指定的子线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源就被系统的其他进程接管并回收了。线程分离之后在主线程中使用pthread_join()就回收不到子线程资源了。
#include <pthread.h>
// 参数就子线程的线程ID, 主线程就可以和这个子线程分离了
int pthread_detach(pthread_t thread);
下面的代码中,在主线程中创建子线程,并调用线程分离函数,实现了主线程和子线程的分离:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 子线程的处理代码
void* working(void* arg)
{
printf("我是子线程, 线程ID: %ld\n", pthread_self());
for(int i=0; i<9; ++i)
{
printf("child == i: = %d\n", i);
}
return NULL;
}
int main()
{
// 1. 创建一个子线程
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("子线程创建成功, 线程ID: %ld\n", tid);
// 2. 子线程不会执行下边的代码, 主线程执行
printf("我是主线程, 线程ID: %ld\n", pthread_self());
for(int i=0; i<3; ++i)
{
printf("i = %d\n", i);
}
// 设置子线程和主线程分离
pthread_detach(tid);
// 让主线程自己退出即可
pthread_exit(NULL);
return 0;
}
- 主线程创建一个子线程
- 主线程和子线程并发执行
- 主线程最后分离子线程并退出
- 子线程自己跑完后自动销毁
6.其他线程函数
6.1 线程取消
线程取消的意思就是在某些特定情况下在一个线程中杀死另一个线程。使用这个函数杀死一个线程需要分两步:
- 在线程A中调用线程取消函数pthread_cancel,指定杀死线程B,这时候线程B是死不了的
- 在线程B中进程一次系统调用(从用户区切换到内核区),否则线程B可以一直运行。
这其实和七步断肠散、含笑半步癫的功效是一样的,吃了毒药不动或者不笑也没啥事儿
#include <pthread.h>
// 参数是子线程的线程ID
int pthread_cancel(pthread_t thread);
-
参数:要杀死的线程的线程ID
-
返回值:函数调用成功返回0,调用失败返回非0错误号。
在下面的示例代码中,主线程调用线程取消函数,只要在子线程中进行了系统调用,当子线程执行到这个位置就挂掉了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 子线程的处理代码
void* working(void* arg)
{
int j=0;
for(int i=0; i<9; ++i)
{
j++;
}
// 这个函数会调用系统函数, 因此这是个间接的系统调用
printf("我是子线程, 线程ID: %ld\n", pthread_self());
for(int i=0; i<9; ++i)
{
printf(" child i: %d\n", i);
}
return NULL;
}
int main()
{
// 1. 创建一个子线程
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("子线程创建成功, 线程ID: %ld\n", tid);
// 2. 子线程不会执行下边的代码, 主线程执行
printf("我是主线程, 线程ID: %ld\n", pthread_self());
for(int i=0; i<3; ++i)
{
printf("i = %d\n", i);
}
// 杀死子线程, 如果子线程中做系统调用, 子线程就结束了
pthread_cancel(tid);
// 让主线程自己退出即可
pthread_exit(NULL);
return 0;
}
关于系统调用有两种方式:
直接调用Linux系统函数
调用标准C库函数,为了实现某些功能,在Linux平台下标准C库函数会调用相关的系统函数
最关键的执行流程(必须懂)
-
主线程调用 pthread_cancel (tid)
→ 只是标记子线程 “要取消”,不会立刻杀死。
-
子线程还在跑 j++ 循环
→ 纯用户态计算,不检查取消,继续活着。
-
子线程终于执行 printf
→ 进入内核态 → 检查取消标记 → 立刻自杀!
-
子线程后面的 printf 全都不会执行!
运行结果一定是这样(大概率)
子线程创建成功, 线程ID: xxx
我是主线程, 线程ID: yyy
i = 0
i = 1
i = 2
6.2 线程ID比较
在Linux中线程ID本质就是一个无符号长整形,因此可以直接使用比较操作符比较两个线程的ID,但是线程库是可以跨平台使用的,在某些平台上 pthread_t可能不是一个单纯的整形,这中情况下比较两个线程的ID必须要使用比较函数,函数原型如下:
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
参数:t1 和 t2 是要比较的线程的线程ID
返回值:如果两个线程ID相等返回非0值,如果不相等返回0
7.进程 vs 线程 异同点
(1)相同点
- 都是操作系统并发执行的基本单位
- 都有就绪、运行、阻塞、终止等状态
- 都可被 CPU 调度、抢占、切换
- 目的都是提高 CPU 利用率,实现多任务
(2)不同点
| 对比维度 | 进程 Process | 线程 Thread |
|---|---|---|
| 定义 | 程序的一次执行实例 | 进程内的一条独立执行路径 |
| 资源分配单位 | 操作系统资源分配最小单位 | 不分配独立资源,共享进程资源 |
| CPU 调度单位 | 不是调度单位 | 操作系统CPU 调度最小单位 |
| 地址空间 | 独立虚拟地址空间,进程间完全隔离 | 共享所属进程地址空间,线程间不隔离 |
| 拥有资源 | 独立内存、堆、全局数据、文件描述符 | 仅私有:栈、寄存器、程序计数器;其余全部共享 |
| 创建 / 销毁开销 | 重量级,开销大、速度慢 | 轻量级,开销极小、速度快 |
| 切换开销 | 进程切换开销很大 | 线程切换开销很小 |
| 通信难度 | 复杂:管道、消息队列、共享内存、信号量 | 简单:直接读写共享变量即可 |
| 稳定性 | 一个进程崩溃,不影响其他进程 | 一个线程异常崩溃,整个进程全部崩溃 |
| 依赖关系 | 可以独立存在运行 | 不能独立存在,必须依附进程 |
| 并发粒度 | 粗粒度,进程间并发 | 细粒度,进程内部并发 |
8.计数
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int count_num = 0; // 共享计数器
mutex mtx; // 互斥锁(保证线程安全)
// 线程函数:安全数数
void add_count()
{
while (true)
{
mtx.lock(); // 加锁(同一时间只有一个线程能进)
if (count_num >= 100)
{
mtx.unlock();
break;
}
count_num++;
cout << "count: " << count_num << endl;
mtx.unlock(); // 解锁
}
}
int main()
{
// 创建两个线程一起数数(演示线程安全)
thread t1(add_count);
thread t2(add_count);
t1.join();
t2.join();
return 0;
}
9.C++线程类
以上线程函数也可以用于C++编程,但是C++11中提供了线程类,感兴趣的可以看一下,链接如下。
参考资料:[线程](线程 | 爱编程的大丙)

浙公网安备 33010602011771号