1.clone函数的使用
#define _GNU_SOURCE
#include <sched.h>
#include <signal.h>int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);
fn:子进程或线程的入口函数
child_stack:子进程的栈地址,通常需要手动分配,栈的大小需要足够容纳子进程变量的局部变量和函数调用。
flags
:控制子进程或线程共享哪些资源(如内存、文件描述符等)。常见的标志包括:CLONE_VM
:共享内存空间。CLONE_FS
:共享文件系统信息。CLONE_FILES
:共享文件描述符。CLONE_SIGHAND
:共享信号处理机制。CLONE_THREAD
:让子进程成为与父进程同一线程组的线程。CLONE_NEWNS
:创建新的 mount namespace。CLONE_NEWUTS
:创建新的 UTS namespace,用于隔离主机名。arg
:传递给fn
的参数。
代码示例
stack+STACK_SIZE是因为开辟的栈区使用是从高到低的,开辟栈区是从低地址到高地址,开辟后返回的地址是低地址,要用高地址使用,开辟的地址加上size就是使用的地址。
#include <sched.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#define STACK_SIZE (1024*1024) //1MB的栈空间
//进程间通信,传递退出码
//子进程执行的函数
static int child_func(void* arg)
{
while(true)
{
printf("Child process:PID=%d\n",getpid());
sleep(1);
}
return 0;
}
int main()
{
char* stack=(char*)malloc(STACK_SIZE);//为子进程分配栈空间
if(stack==NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
//使用clone创建子进程
pid_t pid=clone(child_func,stack+STACK_SIZE,CLONE_VM|SIGCHLD,NULL);
if(pid==-1)
{
perror("clone");
free(stack);
exit(EXIT_FAILURE);
}
printf("Parent process:PID=%d,child PID=%d\n",getpid(),pid);
//等待子进程结束
if(waitpid(pid,NULL,0)==-1)
{
perror("waitpid");
free(stack);
exit(EXIT_FAILURE);
}
free(stack);
return 0;
}
注意:虽然栈空间是独立的,但是线程的地址都是一起的,所以可以访问其它线程的栈空间
代码示例
可以在主线程看到创建线程的变量,另外在创建的新线程死循环会报段错误,因为线程的调度和切换,操作系统可能会回收栈空间,这样就会把a回收,主线程就无权限访问了。
#include <sched.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
int* p=nullptr;
void* threadrun(void* args)
{
int a=123;
p=&a;
//while(true) {sleep(1);}
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,threadrun,nullptr);
while(true)
{
std::cout<<"*p:"<<*p<<std::endl;
sleep(1);
}
pthread_join(tid,nullptr);
return 0;
}
代码示例二
Thread.hpp文件
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadMoudle
{
static int number = 1; // bug
class Thread
{
//把MAIN的方法进行包装
using func_t =std::function<void()>;
private:
void EnableDetach()
{
std::cout<<"线程被分离"<<std::endl;
_isdetach=true;
}
void EnableRunning()
{
_isrunning=true;
}
//传的是this指针,所以是thread*类型
static void* Routine(void* args)//属于类内的成员函数,默认包含this指针,会多一个参数导致参数不匹配
{//这里会把私有成员进行改变,因为执行流已经开始了,分离状态和运行状态此刻要确定好
Thread* self=static_cast<Thread*>(args);
self->EnableRunning();
if(self->_isdetach)
self->Detach();
pthread_setname_np(self->_tid,self->_name.c_str());
self->_func();
return nullptr;
}
public:
//要接受执行什么任务
Thread(func_t func)
:_tid(0),
_isdetach(false),
_isrunning(false),
_res(nullptr),
_func(func)
{
_name="thread-"+std::to_string(number++);
}
void Detach()
{
if(_isdetach)
return;
if(_isrunning)
pthread_detach(_tid);
EnableDetach();
}
bool Start()
{
if(_isrunning)
return false;
int n=pthread_create(&_tid,nullptr,Routine,this);
if(n!=0)
{
std::cerr<<"create pthread error"<<strerror(n)<<std::endl;
return false;
}
else
{
std::cout<<_name<<"create success"<<std::endl;
return true;
}
}
bool Stop()
{
if(_isrunning)
{
int n=pthread_cancel(_tid);
if(n!=0)
{
std::cerr<<"cancel thread error"<<strerror(n)<<std::endl;
return false;
}
else
{
_isrunning =false;
std::cout<<_name<<"stop"<<std::endl;
return true;
}
}
return false;
}
void Join()
{
if(_isdetach)
{
std::cout<<"你的线程已经是分离的,不能进行join"<<std::endl;
return;
}
int n=pthread_join(_tid,&_res);
if(n!=0)
{
std::cerr<<"fail"<<strerror(n)<<std::endl;
}
else
{
std::cout<<"join success"<<std::endl;
}
}
~Thread()
{}
private:
pthread_t _tid;
std::string _name;
bool _isdetach;
bool _isrunning;
func_t _func;
void* _res;
};
}
#endif
MAIN.cc文件
#include "Thread.hpp"
#include <unistd.h>
#include <vector>
using namespace ThreadMoudle;
int main()
{
// std::vector<Thread> threads;
// for(int i=0;i<10;i++)
// {
// threads.emplace_back([](){
// while(true)
// {
// char name[128];
// pthread_getname_np(pthread_self(),name,sizeof(name));
// std::cout<<"我是一个新线程"<<name<<std::endl;//线程名字
// sleep(1);
// }
// });
// }
// for(auto& thread:threads)
// {
// thread.Start();
// }
// for(auto& thread:threads)
// {
// thread.Join();
// }
//传递执行的任务
Thread t([](){
while(true)
{
char name[128];
pthread_getname_np(pthread_self(),name,sizeof(name));
std::cout<<"我是一个新线程:"<<name<<std::endl;
sleep(1);
}
});
t.Start();
//t.Detach();
sleep(5);
t.Stop();
sleep(5);
t.Join();
return 0;
}
pthread_setname_np函数
#define _GNU_SOURCE
#include <pthread.h>
int pthread_setname_np(pthread_t thread, const char *name);
thread
:要设置名称的线程的 ID。name
:要设置的线程名称,长度限制为 16 个字符(包括终止符\0
)。
返回值
成功时返回 0。
出错时返回非零错误码。
错误码
ERANGE
:name
的长度超过了允许的限制。
pthread_getname_np函数
#define _GNU_SOURCE
#include <pthread.h>
int pthread_getname_np(pthread_t thread, char *name, size_t len);
参数
thread
:要获取名称的线程的 ID。name
:一个字符数组,用于存储获取到的线程名称。len
:name
数组的长度。
返回值
成功时返回 0。
出错时返回非零错误码:
EINVAL
:len
小于 1 或大于NAME_MAX
(通常是 16)。ESRCH
:指定的线程不存在。
你提到的问题非常关键,这是 C++ 中多线程编程时常见的一个陷阱。在 C++ 中,类的成员函数默认有一个隐含的 `this` 指针参数,这会导致成员函数的签名与 `pthread_create` 或其他线程库要求的回调函数签名不匹配。
你正确地使用了 `static` 关键字来定义 `Routine` 函数。这是因为:
1. **非静态成员函数**:非静态成员函数会隐含一个 `this` 指针作为第一个参数,这会导致函数签名与 `pthread_create` 需要的回调函数签名不匹配。
2. **静态成员函数**:静态成员函数没有 `this` 指针,因此可以作为线程回调函数。### 关键点解释
1. **静态成员函数作为回调**:
- `Routine` 是一个静态成员函数,没有隐含的 `this` 指针。
- 它的签名与 `pthread_create` 需要的回调函数签名完全匹配:`void* (*)(void*)`。2. **传递 `this` 指针**:
- 在调用 `pthread_create` 时,将 `this` 指针作为参数传递给 `Routine`。
- 在 `Routine` 中,使用 `static_cast<Thread*>(args)` 将 `void*` 参数转换回 `Thread*` 类型。3. **线程生命周期管理**:
- 在 `Thread` 的构造函数中调用 `pthread_create` 创建线程。
- 在 `Thread` 的析构函数中调用 `pthread_join` 等待线程结束,确保线程安全退出。### 总结
你提到的问题非常关键,C++ 的成员函数由于隐含的 `this` 指针,不能直接用作线程回调函数。通过将回调函数定义为静态成员函数,并将 `this` 指针作为参数传递,可以解决这个问题。希望这个解决方案对你有帮助!
多线程创建执行任务
把方法放到vector里面存储,然后范围foe逐一执行任务。
#include "thread.hpp"
#include <unistd.h>
#include <vector>
#include <iterator> // 或 <algorithm>
#include<algorithm>
using namespace ThreadMoudle;
int main()
{
std::vector<Thread> threads;
for (int i = 0; i < 10; i++)
{
threads.emplace_back([]()
{
while(true)
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
sleep(1);
} });
}
for (auto &thread : threads)
{
thread.Start();
}
for (auto &thread : threads)
{
thread.Join();
}
// Thread t([](){
// while(true)
// {
// char name[128];
// pthread_getname_np(pthread_self(), name, sizeof(name));
// std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
// sleep(1);
// }
// });
// t.Start();
// t.Detach();
// sleep(5);
// t.Stop();
// sleep(5);
// t.Join();
return 0;
}
模板参数形式
Thread.cpp文件
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadModlue
{
static uint32_t number = 1; // bug
template<typename T>
class Thread
{
using func_t = std::function<void(T)>; // 暂时这样写,完全够了
private:
void EnableDetach()
{
std::cout << "线程被分离了" << std::endl;
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
{
Thread<T> *self = static_cast<Thread<T> *>(args);
self->EnableRunning();
if (self->_isdetach)
self->Detach();
self->_func(self->_data); // 回调处理
return nullptr;
}
// bug
public:
Thread(func_t func, T data)
: _tid(0),
_isdetach(false),
_isrunning(false),
res(nullptr),
_func(func),
_data(data)
{
_name = "thread-" + std::to_string(number++);
}
void Detach()
{
if (_isdetach)
return;
if (_isrunning)
pthread_detach(_tid);
EnableDetach();
}
bool Start()
{
if (_isrunning)
return false;
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << _name << " create success" << std::endl;
return true;
}
}
bool Stop()
{
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
return false;
}
else
{
_isrunning = false;
std::cout << _name << " stop" << std::endl;
return true;
}
}
return false;
}
void Join()
{
if (_isdetach)
{
std::cout << "你的线程已经是分离的了,不能进行join" << std::endl;
return;
}
int n = pthread_join(_tid, &res);
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
}
else
{
std::cout << "join success" << std::endl;
}
}
~Thread()
{
}
private:
pthread_t _tid;
std::string _name;
bool _isdetach;
bool _isrunning;
void *res;
func_t _func;
T _data;
};
}
#endif
Main.cc文件
可以传递多类型参数执行任务,传递对象,可以设置多个成员,这样执行完后,就可以把得到执行完后的成员变量的值。
#include "Thread.hpp"
#include <unistd.h>
using namespace ThreadModlue;
// 我们可以传递对象吗???
// class ThreadData
// {
// public:
// pthread_t tid;
// std::string name;
// };
// void Count(ThreadData td)
// {
// while (true)
// {
// std::cout << "我是一个新线程" << std::endl;
// sleep(1);
// }
// }
int main()
{
// ThreadData td;
// Thread<ThreadData> t(Count, td);
// t.Start();
// t.Join();
// Thread t([](){
// while(true)
// {
// std::cout << "我是一个新线程" << std::endl;
// sleep(1);
// }
// });
// t.Start();
// t.Detach();
// sleep(5);
// t.Stop();
// sleep(5);
// t.Join();
return 0;
}
线程局部存储
两个线程,一个打印并改变变量,一个只打印,可以看到变量是一起改变的,所以要想这个变量不被其它线程看到,就需要加__thread前缀,线程局部存储也有限制,只能存储内置类型和部分指针。
代码示例
#include <pthread.h>
#include <iostream>
#include <string>
#include <unistd.h>
// 该count叫做线程的局部存储!
__thread int count = 1;
// 线程局部存储有什么用?全局变量,我又不想让这个全局变量被其他线程看到!
// 线程局部存储,只能存储内置类型和部分指针
std::string Addr(int &c)
{
char addr[64];
snprintf(addr, sizeof(addr), "%p", &c);
return addr;
}
void *routine1(void *args)
{
(void)args;
while (true)
{
std::cout << "thread - 1, count = " << count << "[我来修改count], "
<< "&count: " << Addr(count) << std::endl;
count++;
sleep(1);
}
}
void *routine2(void *args)
{
(void)args;
while (true)
{
std::cout << "thread - 2, count = " << count
<< ", &count: " << Addr(count) << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, nullptr, routine1, nullptr);
pthread_create(&tid2, nullptr, routine2, nullptr);
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
return 0;
}
2.线程互斥
进程线程间的互斥相关背景概念
临界资源:多线程执行流共享的资源就叫临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只用两种状态,要么完成,要么未完成
互斥量mutex
互斥量是一种同步原语,用于保护共享资源,防止多个线程同时访问。在多线程环境中,互斥量可以确保同一时间只有一个线程可以访问特定的资源,从而避免数据竞争和未定义行为。
代码示例
模拟抢票,因为有了锁,所以不会出现票为负数的情况
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 1000;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *route(void *arg)
{
char *id = (char *)arg;
while (1)
{
pthread_mutex_lock(&lock);
if (ticket > 0) // 1. 判断
{
usleep(1000); // 模拟抢票花的时间
printf("%s sells ticket:%d\n", id, ticket); // 2. 抢到了票
ticket--; // 3. 票数--
pthread_mutex_unlock(&lock);
}
else
{
pthread_mutex_unlock(&lock);
break;
}
}
return nullptr;
}
int main(void)
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, (void *)"thread 1");
pthread_create(&t2, NULL, route, (void *)"thread 2");
pthread_create(&t3, NULL, route, (void *)"thread 3");
pthread_create(&t4, NULL, route, (void *)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
初始化互斥量:
pthread_mutex=PTHREAD_NUTEX_INITIALIZER
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);