7 多线程编程
7.1 概念
线程是比进程更小的能独立运行的基本单位,线程基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如线程ID,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个线程包含以下内容:
1、指向当前被执行指令的指令指针;
2、栈;
3、寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值;
4、私有的数据区
查看线程:
man 7 threads
7.2 查看线程
- 命令
命令 | 含义 |
---|---|
|
-T开启线程查看 |
|
-H开启线程查看 |
- 文件
文件 | 含义 |
---|---|
|
线程默认的名字和进程名相同 |
|
线程名 |
7.3 操作
操作 | 函数 |
---|---|
线程标识 |
|
线程创建 |
|
子线程终止 |
|
线程合并 |
|
线程分离 |
|
发送信号 |
|
7.3.1 线程标识
pthread_t pthread_self(void)
-
返回值
当前线程的线程ID
注:
1、线程ID打印使用
%lu
2、
pthread_self()
不链接库
pthread
返回值为0.
linux上的线程实现就是在内核支持的基础上以POSIX thread的方式对外封装了接口。
7.3.2 线程创建
int pthread_create(pthread_t * tidp, pthread_attr_t * attr, void *(*start_rtn)(void), void * arg)
- 参数
参数 | 含义 |
---|---|
|
线程ID指针 |
|
线程属性 |
7.3.3 子线程退出
void pthread_exit(void* retval)
-
参数
retval
:函数的返回指针,只要
pthread_join
中的第二个参数
retval
不是
NULL
,这个值将被传递给
retval
用在线程回调函数中,返回线程数据
-
子线程退出有两种方式
1、线程处理函数
return
。
2、调用子线程终止
7.3.4 线程合并
int pthread_join(pthread_t tid, void **retval)
- 参数
参数 | 含义 |
---|---|
|
被等待的线程标识符 |
|
一个用户定义的指针,它可以用来存储被等待线程的返回值 |
- 返回值
返回值 | 含义 |
---|---|
0 | 成功 |
非0 | 错误码 |
注:可以由其他线程终止,回收资源
7.3.5 线程分离
int pthread_detach(pthread_t tid)
-
参数
tid
:要释放线程的标识符ID -
返回值
返回值 | 含义 |
---|---|
0 | 成功 |
非0 | 错误码 |
注:不能被其他线程终止,存储资源在它终止时由系统自动回收释放
线程分离后不能使用
join
。
7.4 进程线程比较
7.4.1 接口对比
进程 | 线程 | 描述 |
---|---|---|
|
|
获得控制流的id |
|
|
创建新的控制流 |
|
|
退出已有的控制流 |
|
|
等待控制流并获得结束代码 |
7.4.2 特性对比
特性 | 进程 | 线程 |
---|---|---|
粒度 | 系统资源分配和调度的基本单位 | CPU调度和分派的基本单位 |
资源 | 有独立的地址空间 | 共享进程的地址空间 |
效率 | 上下文切换要较慢 | 上下文切换较快 |
稳定性 | 子进程崩溃,不影响父进程与其他子进程 | 任何一个线程崩溃,整个程序崩溃 |
7.4.3 进程与线程的区别
-
概念
进程
: 进程是一个具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统资源分配和独立运行的最小单位;
线程
: 线程是进程的一个执行单元,是任务调度和系统执行的最小单位; -
区别
1)进程是资源(包括内存、打开的文件等)分配的单位,线程是 CPU 调度的单位;
2)进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈;
3)线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;
4)线程能减少并发执行的时间和空间开销;
C++中采用多线程实现并发。
7.4.4 如何选择进程或线程
(1)需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
(2)线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应。
(3)因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
(4)并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;
(5)需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
如何创建进程
:fork()
、
exec()
、系统函数
system()
7.5 C++11 thead类
实例:使用
pthread
实现简单的
thread
类。
#ifndef __THREAD_H
#define __THREAD_H
#include <pthread.h>
namespace miniSTL{
class thread{
typedef pthread_t id;
typedef void* (*func_t)(void*);
typedef void (*funcv_t)();
id _id; // 线程TID
public:
thread():_id(0){}
thread(func_t func){
pthread_create(&_id,NULL,func,NULL);
}
thread(funcv_t func){
pthread_create(&_id,NULL,reinterpret_cast<func_t>(func),NULL);
}
id get_id()const{
return _id;
}
void join(){
pthread_join(_id,NULL);
}
void detach(){
pthread_detach(_id);
}
};
};
#endif // __THREAD_H
7.6 实例
7.6.1 实例1:多线程操作
#include <pthread.h>
#include <unistd.h>
#include <iostream>
//线程并发执行
using namespace std;
void* handle(void* p){
int* pn = (int*)p;
for(int i = 0;i < 5;++i){
sleep(1);
cout << pthread_self() << ":" << --*pn << endl;
}
return NULL;
}
int main(){ //主线程
cout << getpid() << endl;
cout << pthread_self() << endl;
int n = 0;
pthread_t tid;
pthread_create(&tid,NULL,handle,&n); //创建一个子线程
for(int i = 0;i < 5;++i){
sleep(1);
cout << pthread_self() << ":" << ++n << endl;
}
}
[root@localhost 6]# ./a.out
4112
139760152852288
139760152852288:1
139760134895360:0
139760152852288:1
139760134895360:0
139760152852288:1
139760134895360:0
139760152852288:1
139760134895360:0
139760152852288:1
可以看出,主线程和子线程并发进行。
但是若主线程提前退出,则子线程也退出,例如:
#include <pthread.h>
#include <unistd.h>
#include <iostream>
//线程并发执行
using namespace std;
void* handle(void* p){
int* pn = (int*)p;
for(int i = 0;i < 10;++i){
sleep(1);
cout << pthread_self() << ":" << --*pn << endl;
}
return NULL;
}
int main(){
//每个进程里默认有一个线程
//主线程
cout << getpid() << endl;
cout << pthread_self() << endl;
int n = 0;
pthread_t tid;
pthread_create(&tid,NULL,handle,&n); //创建一个子线程
for(int i = 0;i < 5;++i){
sleep(1);
cout << pthread_self() << ":" << ++n << endl;
}
}
4357
139760237700928
139760237700928:1
139760219744000:0
139760237700928:1
139760219744000:0
139760237700928:1
139760219744000:0
139760237700928:1
139760219744000:0
139760237700928:1
可以通过线程合并
pthread_join()
解决
#include <pthread.h>
#include <unistd.h>
#include <iostream>
//线程并发执行
using namespace std;
void* handle(void* p){
int* pn = (int*)p;
for(int i = 0;i < 10;++i){
sleep(1);
cout << pthread_self() << ":" << --*pn << endl;
}
return NULL;
}
int main(){
//每个进程里默认有一个线程
//主线程
cout << getpid() << endl;
cout << pthread_self() << endl;
int n = 0;
pthread_t tid;
pthread_create(&tid,NULL,handle,&n); //创建一个子线程
for(int i = 0;i < 5;++i){
sleep(1);
cout << pthread_self() << ":" << ++n << endl;
}
pthread_join(tid,NULL);
}
4428
140245492279104
140245492279104:1
140245474322176:0
140245492279104:1
140245474322176:0
140245492279104:1
140245474322176:0
140245492279104:1
140245474322176:0
140245492279104:1
140245474322176:0
140245474322176:-1
140245474322176:-2
140245474322176:-3
140245474322176:-4
140245474322176:-5
7.6.2 实例2:为多线程加入信号量
在多线程编程中,为防止数据竞争,通常需要加入多线程同步,主要方式有信号量、互斥量、条件变量和读写锁四种方式。
实例:为多线程加入信号量
#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <semaphore.h>
//线程并发
sem_t sem;
using namespace std;
void* handle(void* p) {
int* pn = (int*)p;
for(int i = 0; i < 10; i++) {
sleep(1);
sem_wait(&sem);
cout << pthread_self() << ":" << --*pn << endl;
sem_post(&sem);
}
delete pn;
return NULL;
}
pair<pthread_t,int*> test() {
// int* p = new int(0); //p存放默认0
int* p = new int; //p存放默认0
pthread_t tid;
pthread_create(&tid,NULL,handle,p);
//return pair<pthread_t,int*>(tid,p);
return {tid,p};
}
int main() { // 主线程
sem_init(&sem,0,1);
cout << getpid() << endl;
cout << pthread_self() << endl;
/*C++11
auto pi = test();
tid = pi.first;
int* p = pi.second;
*/
auto [tid,p] = test();//C++17
for(int i = 0; i < 5; i++) {
sleep(1);
sem_wait(&sem);
cout << pthread_self() << ":" << ++*p << endl;
sem_post(&sem);
}
pthread_join(tid,NULL);
sem_destroy(&sem);
return 0;
}
[root@localhost 6]# ./a.out
5431
140018458928960
140018458928960:1
140018440972032:0
140018458928960:1
140018440972032:0
140018458928960:1
140018440972032:0
140018458928960:1
140018440972032:0
140018458928960:1
140018440972032:0
140018440972032:-1
140018440972032:-2
140018440972032:-3
140018440972032:-4
140018440972032:-5