漫话linux:线程控制与多线程

#哪个编程工具让你的工作效率翻倍?#

1.需要注意的是,内核中并没有线程的概念,只有轻量化进程的系统调用,但是用户需要线程的接口

2.所以linux开发者提供了pthread线程库-应用层-轻量级接口,为用户提供线程的接口:基本所有linux平台,都默认自带这个库,linux代码编写多线程代码时需要调用pthread第三方库

3.为了接受各种指针类型,接口被设置成了void*,之后强转即可,windows指针4个比特位,linux8个比特位

4.pthread_create:创建一个新的线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数分析:

        1.thread:输出型参数,获取创建成功的线程ID(地址)

        2.attr:线程属性控制,默认属性NULL

        3.start_routine:函数地址,线程启动后要执行的函数

        4.arg:传给线程启动函数的参数,不需要时设置为NULL(谣传就必须是指针)

返回值:成功返回0,失败返回错误码

测试,可以发现两个执行流

#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstdio>
 
using namespace std;
 
void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    string name=static_cast<const char*>(args);
    while(true)
        {
            cout<<"new thread create success, name: "<<name<<endl;
            sleep(1);
        }
}
 
int main()
{
 
    #define NUM 10
    for(int i=0;i<NUM;++i)
        {
            pthread_t id;
            //pthread_create(&id,nullptr,start_rountine,(void*)"thread new"); 
            char namebuffer[64];
            snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);
            pthread_create(&id,nullptr,start_rountine,namebuffer); 
        }
 
 
    while(true)
        {
            cout<<"new thread create success, name: main thread"<<endl;
            sleep(1);
        }
 
    return 0;
}

首先是函数start_rountine函数的解析,首先它传入的是一个void*类型的指针,再使用static_cast转化为一个字符串类型,如果需要也可以传入char *函数,此函数为线程调用的函数,由于返回类型为void*,所以也可以不返回

然后是主函数解析,首先是创建10个线程,首先初始化线程标识符和namebuffer数组(64位),然后是snprintf函数来格式化只发出并将该字符串安全的写入到创建线程的函数中

第二行: snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);

参数分析:

        1.namebuffer目标缓冲区

        2.sizeof(namebuffer)表示缓冲区的大小

        3.%s:%d格式字符串,表示一个字符串后面跟一个冒号一个整数

        4."thread"要插入的第一个参数是一个字符串thread

        5.i是要插入的第二个参数

第三行: pthread_create(&id,nullptr,start_routine,namebuffer);

namebuffer: 这是传递给线程启动例程 start_routine 的参数,即包含 "thread:i" 的字符串

线程的第三方库,不是系统调用,g++后需要-lpthread链接库

ps-aL查看轻量级进程(LWP)

 PID==LWP ,表面这个线程是主线程

任何一个线程被干掉了,进程就会被干掉,一个函数可以被多个执行流同时执行,这叫被重入现象,全局变量所有线程共享,都可以访问操作,调度器决定线程次序,我们无法控制,但肯定是主线程退出

循环监视窗口的打开:while :; do ps -aL | grep mythread;sleep 1;done

线程等待:类似于僵尸,需要等待退出,获取子进程退出结果

int pthread_join(pthread_t thraad,void**retval)

参数分析:

        1.thread线程ID

        2.retval利用其带出线程的返回值

返回值:成功返回0,失败返回错误码

 

main thread等待的时候,默认是阻塞等待的

主进程需要join:对创建线程进行回收

void** retval :指向指针的指针,为了调用了接口,获取线程退出的退出码

对指针解引用,代表指针所指向的目标 

将拿到的返回值空间存储到自己的地址空间中,所以就是一个二级指针了,保证了实参的传递,取地址解引用得到函数返回值,存取到用户指针中。如解两次引用就可以获取函数原值了

直接调用 exit,会全部都直接退出

exit是用来终止进程的!不能用来直接终止线程!

5.线程终止:

        1.return

        2.pthread_exit

void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    ThreadDate* td=static_cast<ThreadDate*>(args);
    int cnt=10;
    while(cnt)
    {
        cout<<"cnt: "<<cnt<<" &cnt"<<&cnt<<endl;
        cnt--;
        sleep(1);
        pthread_exit(nullptr);
    }
    return nullptr;
}

 代码解析,首先传入的是启动线程需要的数据,并将其安全的转化为ThreadDate,然后初始化一个计数器cnt为10,然后进入一个循环,每次循环都会打印出cnt的值和地址,然后cnt减1,接着调用sleep(1)使线程休眠1,不过return nullptr没有必要,因为到了pthread_exit被调用就会退出

pthread_cancel -1(线程取消)

库设置的返回值,可以查看到 一个线程如果是被取消的,退出码是-1

#include <iostream>  
#include <vector>  
#include <thread>  
#include <cassert>  
#include <mutex> // 用于线程安全的输出  
  
// 假设的 ThreadDate 类  
class ThreadDate {  
public:  
    ThreadDate(int id) : id_(id) {}  
    void doSomething() {  
        // 模拟一些工作  
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  
        std::lock_guard<std::mutex> lock(coutMutex); // 锁定输出流以进行线程安全的打印  
        std::cout << "ThreadDate " << id_ << " is doing something.\n";  
    }  
  
private:  
    int id_;  
    static std::mutex coutMutex; // 用于保护 std::cout 的互斥锁  
};  
  
// 初始化静态成员变量  
std::mutex ThreadDate::coutMutex;  
  
// 线程函数  
void* threadFunction(void* arg) {  
    ThreadDate* td = static_cast<ThreadDate*>(arg);  
    td->doSomething();  
    return nullptr;  
}  
  
int main() {  
    const int numThreads = 5;  
    std::vector<ThreadDate*> threadDates;  
    std::vector<pthread_t> threads;  
  
    // 创建 ThreadDate 对象和线程  
    for (int i = 0; i < numThreads; ++i) {  
        ThreadDate* td = new ThreadDate(i);  
        threadDates.push_back(td);  
  
        // 使用 pthread 创建线程(注意:这里使用了 pthread,但 C++11 提供了 std::thread 作为更现代的选择)  
        pthread_t pthread;  
        int result = pthread_create(&pthread, nullptr, threadFunction, td);  
        assert(result == 0); // 检查线程是否成功创建  
        threads.push_back(pthread);  
    }  
  
    // 等待所有线程完成  
    for (pthread_t& thread : threads) {  
        pthread_join(thread, nullptr);  
    }  
  
    // 清理资源  
    for (ThreadDate* td : threadDates) {  
        delete td;  
    }  
  
    threadDates.clear();  
    threads.clear();  
  
    return 0;  
}

代码解析:首先是ThreadDate类,包含线程的id和一个函数,该函数有以下操作:让线程休眠一段时间,锁定输出流进行安全打印,并且打印标准语句,私有成员除了id还有一个互斥锁

线程函数:传入线程数据,强转之后再调用线程

主函数:首先声明一个常量整数用于限制线程的数量,再设计一个vector来存储对象的指针,还有一个vector来存储线程的标识符,然后循环创造新的对象,再使用pthread创建线程并调用线程函数,再进行等待和清理工作

多线程的实现代码和原理

void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    ThreadDate* td=static_cast<ThreadDate*>(args);
    int cnt=5;
    while(cnt)
    {
        cout<<"cnt: "<<cnt<<" &cnt"<<&cnt<<endl;
        cnt--;
        sleep(1);
    }
 
    //正常跑完返回的100,那被取消的线程返回的是什么呢?
    return (void*)100;
}
 
 
int main()  
{
    vector<ThreadDate*> threads;
#define NUM 10
    for(int i=0;i<NUM;++i)
    {
 
        ThreadDate* td=new ThreadDate();
        td->number=i+1;
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i+1);
        pthread_create(&td->tid,nullptr,start_rountine,td); 
        //这样不仅每个线程数据除了自己拿到了,主线程也全部拿到了
        threads.push_back(td);
    }
 
 
    for(auto& iter:threads)
    {
        cout<<"create thread: "<<iter->namebuffer<<" : "<<iter->tid<<" success"<<endl;
    }
 
    //线程取消
    sleep(5);//先让线程跑起来
    for(int i=0;i<threads.size()/2;++i)
    {
        pthread_cancel(threads[i]->tid);//创建多线程组中的某一 进行取消
        cout<<"pthread_cancel: "<<threads[i]->namebuffer<<" success"<<endl;
    }
 
    for(auto& iter:threads)
    {
        void* ret=nullptr;//注意是void*
        int n=pthread_join(iter->tid,&ret);//&地址是void**  
        assert(n == 0);
        (void)n;
        cout<<"join : "<<iter->namebuffer<<" success , number: "<<(long long)ret<<endl;
        delete iter;
    }
    //这里就可以看出主线程式阻塞式的等待,全部等待成功,才打印这句话
    cout<<"main thread quit!!"<<endl;
 
    return 0;
} 

主函数解析:首先还是存储对象的vector和线程数,然后循环创建线程,初始化线程编号,调用snprintf来初始化线程的标识,再创建线程并让主线程拿到数据 (td为对象指针被传入线程,每个线程都可以使用td并且利用其修改对象的数据,而td也被主线程(main函数)所使用,实现了子线程和主线程的数据共享),然后进行运行等待和清理

6.线程间通信

线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递类的对象!!

举例:实现通信

结构体+初始化=>类

 

将 rq 的内容传给线程函数,对 rq 获取内容执行 rsp 运算,释放 rq,返回 rsp 打印

要用地址符号接受,接收到的也是回复类的地址

pthread_join(tid, &ret);//接收指针的地址,实现传递

Response *rsp = static_cast<Response *>(ret);

   1 #include <iostream>  
    2 #include <vector>  
    3 #include <thread>  
    4 #include <cassert>  
    5 #include <mutex> // 用于线程安全的输出  
    6 #include<cstring>
    7 using namespace std;
    8 class Request
    9 {
   10 public:
   11     Request(int start, int end, const string &threadname)
   12     : start_(start), end_(end), threadname_(threadname)
   13     {}
   14 public:
   15     int start_;
   16     int end_;
   17     string threadname_;
   18 };
   19  
   20 class Response
   21 {                                                                                                                 
   22 public:
   23     Response(int result, int exitcode):result_(result),exitcode_(exitcode)
   24     {}
   25 public:
   26     int result_;   // 计算结果
  int exitcode_; // 计算结果是否可靠
   28 };
   29 void *sumCount(void *args) // 线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!!
   30 {                                                                                                                 
   31     Request *rq = static_cast<Request*>(args); //  Request *rq = (Request*)args
   32     //强制转化
   33     Response *rsp = new Response(0,0);
   34     for(int i = rq->start_; i <= rq->end_; i++)
   35     {
   36         cout << rq->threadname_ << " is runing, caling..., " << i << endl;
   37         rsp->result_ += i;
E> 38         usleep(100000);
   39     }
   40     delete rq;//释放空间
   41     return rsp;
   42 }
   43  
   44 int main()
   45 {
   46     pthread_t tid;
   47     Request *rq = new Request(1, 100, "thread 1");//参数指针
   48     pthread_create(&tid, nullptr, sumCount, rq);
   void *ret;
   52     pthread_join(tid, &ret);//接收指针的地址,实现传递
   53     Response *rsp = static_cast<Response *>(ret);
   54     cout << "rsp->result: " << rsp->result_ << ", exitcode: " << rsp->exitcode_ << endl;
   55     delete rsp;
   56     return 0;
   57 }
  ~



 

代码解析:

request需求类,有三个对象,开始,结束和线程名,response有两个成员,一个用于计算结果另一个用于计算结果是否可靠,然后是sumcount函数(传入线程标识指针),先强转为request对象指针,初始化response的两个成员,然后从request的开始到结尾,打印信息并更新response,再释放空间,主函数就是创建一个request对象再传进去,进行线程通信(首先创建进程并获取返回值,类型转化并打印结果,实现子线程与主线程的通信)

也可以反映出堆空间也是被线程共享的

        1.目前,我们的原生线程,pthread库,原生线程库

        2.c++11语言本身也已经支持多线程了 vs 原生线程库 

C++ 的多线程就是封装原生线程库(thread)

void threadrun()
{
    while(true)
    {
        cout << "I am a new thead for C++" << endl;
        sleep(1);
    }
}
 
int main()
{
    thread t1(threadrun);//调用
 
    t1.join();//等待
 
    return 0;
}

thread t1(threadrun); //调用线程函数

编译时注意!两个编译后缀都要带 g++ -o $@$^ -std=c++11 -lpthread

windows 下装的是 windows c++的库,安装的是不同平台的库,所以语言具有跨平台性

打印线程id

pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");
cout<<"thread id:"<<pthread_self()<<endl;
 
 
//对16进制转化的实现
std::string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);//重点研究理解
    return hex;
}

man clone专门用来创建轻量级进程,我们使用的库底层就是它

线程的概念,是库给我们来维护的,你用的原生线程库,要不要加载到内存里,加载到哪里?

要--都是基于内存的

线程库要维护线程概念--不用维护线程的执行流,线程库注定了要维护多个线程属性集合。线程库要不要管理这些线程呢?要,先描述再组织

由用户维护,OS 之上的,所以称为用户级线程

库加载到了共享区,在堆栈之间

每一个线程的库级别的 tcb 的起始地址,叫做线程的 tid

除了主线程:,所有其他线程的独立栈,都在共享区,具体来讲是在 pthread 库中,tid 指向的用户 tcb 中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值