线程控制(万字详解)

目录

pthread_create(创建线程)

接口返回值

运行代码(注意事项)

执行报错

LWP(light weight process)

线程不具有独立性

全局变量

线程等待—>pthread_join

代码演示

线程终止—>pthread_exit和return

代码演示

线程取消——>pthread_cancel

代码演示

这里返回值为什么为-1呢?

语言的跨平台性

获取自己的线程tid----->pthread_self

从线程tid谈起

测试独立栈

如何在主线程中获新线程的数据?(禁止操作)

线程局部存储

 共享资源

怎么给线程分配私有的全局变量呢?(线程局部存储)

线程局部存储的作用是什么?

线程不是有独立栈嘛,为什么不直接使用一个局部变量保存?

tid与LWP


pthread_create(创建线程)

第一个参数为系统给我们封装的结构,可以理解为新创建的线程ID,输出型参数。

第二个参数可以设置线程的属性,比如栈的大小,一般都把这个参数设置为nullptr

第三个参数(routine:例程)函数指针,返回值为oid*,参数也为void*,本质就是一个回调函数,调用新创建的线程去执行函数指针所指向的方法,值得注意的是main就是主线程的入口函数

还有一个我们需要关注的点,这里是void*而不是void,void是不能定义变量,因为void的大小是不确定的无法分配空间,void*是能用来定义变量的,并且它能够用来接收和返回任意指针类型(在C语言中可以让代码泛型),接收字符串常量时,要强转比如:(void*)"thread-1";

在windows下默认为32位,指针大小位4字节

在Linux下,默认位64位

第四个参数是输入型参数,创建线程成功,新线程回调线程函数的时候,需要参数。这个参数就是给线程函数传递的,不需要,传nullptr

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

int val=100;

void* routine(void*arg)
{
    const char*name=(const char*)arg;
    while(1)
    {
        printf("name:%s,pthread pid:%d val:%d &val:%p\n",name,getpid(),val,&val);
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");

    while(1)
    {
        printf("main pthread pid:%d val:%d &val:%p,tid:%p\n",getpid(),val,&val,tid);
        sleep(1);
        val++;
    }

    return 0;
}

接口返回值

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

运行代码(注意事项)

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

void* routine(void*arg)
{
    while(1)
    {
        cout<<"new pthread:"<<getpid()<<endl;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,nullptr);

    while(1)
    {
        cout<<"main pthread:"<<getpid()<<endl;
        sleep(1);
    }

    return 0;
}
执行报错

命令行执行,链接时报错

记住一点,pthread_create接口不是系统调用,而是一个库方法,pthred库是一个第三方库

需要链接,才能使用,使用手册里也有所说明。因为pthread本来就在系统里,所有只需要-l

LWP(light weight process)

使用ps ajx查看进程也只看到了一个对应进程,因此我们确实是创建了一个新的执行流

ps -aL查看所有线程 L:light(轻)

在这里我们看到了两个mythread,同一个进程内的两个执行流,相同的PID

LWP:轻量级进程标识符,CPU调度时就是按照LWP调度的

我们发现有一个mythread的PID和LWP相同,这代表这个就是主线程,不同就是新线程

操作系统也是依靠这个特性区分是主线程还是新线程

线程不具有独立性

任何一个线程因为异常问题被关掉,默认整个进程都会被干掉

发送信号是发给进程,还是发给线程?
在这里,我们认为是发个进程的,因为每一个线程都是进程的一个执行分支

全局变量

无论是已初始化还是未初始化的,所有线程共享,因为和

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

int val=100;

void* routine(void*arg)
{
    while(1)
    {
        printf("new pthread pid:%d val:%d &val:%p\n",getpid(),val,&val);
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,nullptr);

    while(1)
    {
        printf("main pthread pid:%d val:%d &val:%p\n",getpid(),val,&val);
        sleep(1);
        val++;
    }

    return 0;
}

通过打印我们发现tid并不是LWP,因此LWP只是操作系统层的概念,用户层并没有这个概念

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,nullptr);

    while(1)
    {
        printf("main pthread pid:%d val:%d &val:%p,tid:%p\n",getpid(),val,&val,tid);
        //cout<<"main pthread:"<<getpid()<<endl;
        sleep(1);
        val++;
    }

    return 0;
}

线程等待—>pthread_join

我们应该明确一点,主线程和新创建的线程,谁先跑,我们可能不清楚,但谁最后退出,我们应该清楚

线程等待的两个目的:防止造成内存泄漏,获取新线程的退出状态

代码演示

main thread等待时,默认为阻塞等待

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

int val=100;

void* routine(void*arg)
{
    const char*name=(char*)arg;
    int cnt=5;
    while(1)
    {
        printf("name:%s,pthread pid:%d val:%d &val:%p\n",name,getpid(),val,&val);
        //cout<<"new pthread:"<<getpid()<<endl;
        cnt--;
        if(cnt==0)
        {
            break;
        }
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");

    pthread_join(tid,nullptr);
    sleep(5);
    cout<<"main quit..."<<endl;

    return 0;
}

接收线程的返回值

可以看到的是线程执行函数的返回值是void*,那里在主线程里也应该定义一个void*,要将函数里面的值获取出来,我们需要传void*类型变量的地址或引用,但在C语言里没有引用,所以这这里使用了两级指针作为参数。

补充一点:任何类型传参都会形成临时变量,进行拷贝,形参实例化

x为形参实列化,retval是我们自己定义的void*类型参数,??代表函数的返回值,这个操作是在pthread库里面操作的,因为pthread_join是库接口

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

int val=100;

void* routine(void*arg)
{
    const char*name=(char*)arg;
    int cnt=5;
    while(1)
    {
        printf("name:%s,pthread pid:%d val:%d &val:%p\n",name,getpid(),val,&val);
        cnt--;
        if(cnt==0)
        {
            break;
        }
        sleep(1);
    }

    return (void*)1;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");

    void*ret;
    pthread_join(tid,&ret);
    sleep(5);
    cout<<"main quit... ret::"<<(long long int)ret<<endl;//因为在64位机器下,指针为8字节,所以不能强转为(int4字节)

    return 0;
}

为什么我们在join的时候不考虑异常呢?

做不到,一个线程出现异常,整个进程都崩了

线程终止—>pthread_exit和return

exit是用来终止进程的,不能用来终止线程

主线程退出,其它线程都要退出

代码演示

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

int val=100;

void* routine(void*arg)
{
    const char*name=(char*)arg;
    int cnt=5;
    while(1)
    {
        printf("name:%s,pthread pid:%d val:%d &val:%p\n",name,getpid(),val,&val);
        cnt--;
        if(cnt==0)
        {
            break;
        }
        sleep(1);
    }

    pthread_exit((void*)100);

   

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");

  

    void*ret;
    pthread_join(tid,&ret);
    cout<<"main quit... ret::"<<(long long int)ret<<endl;//因为在64位机器下,指针为8字节,所以不能强转为(int4字节)
    
    return 0;
}

线程取消——>pthread_cancel

无论新线程是否执行完,立即取消一个线程

代码演示

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

int val=100;

void* routine(void*arg)
{
    const char*name=(char*)arg;
    int cnt=5;
    while(1)
    {
        printf("name:%s,pthread pid:%d val:%d &val:%p\n",name,getpid(),val,&val);
        cnt--;
        if(cnt==0)
        {
            break;
        }
        sleep(1);
    }

    pthread_exit((void*)100);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");
    sleep(1);
    pthread_cancel(tid);

    void*ret;
    pthread_join(tid,&ret);
    cout<<"main quit... ret::"<<(long long int)ret<<endl;//因为在64位机器下,指针为8字节,所以不能强转为(int4字节)
   
    return 0;
}

这里返回值为什么为-1呢?

如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED(宏)

语言的跨平台性

语言之所以具有跨平台性,是因为在不同平台下,安装不同的库版本 

库的不同保证了语言的可移植性

拿C++11的多线程举例

在Linux下,C++11中的多线程 ,底层是用Linux中的pthread原生线程库封装实现的

在下面代码中,完全使用的是C++11多线程,但是在指令编译依旧不变

在Windows下,C++11中的多线程 ,底层是Windows的线程接口封装实现的

一般情况下,都推荐直接使用语言的线程库,因为系统调用接口是不具备可移植性的。

获取自己的线程tid----->pthread_self

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;

string to_hex(pthread_t tid)
{
    char hex[64];
    snprintf(hex,sizeof(hex),"%p",tid);

    return hex;
}

void* func(void* arg)
{
    const char* name=static_cast<const char*> (arg);
    while(1)
    {
        cout<<"name:"<<name<<" tid:"<<to_hex(pthread_self())<<endl;
        sleep(1);
    }

    return nullptr;
}


int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,func,(void*)"thread-1");

    printf("main thread,new thread id:%p\n",tid);

    pthread_join(tid,nullptr);

    return 0;
}

从线程tid谈起

clone接口创建轻量级线程,pthread原生线程库里封装了这个接口,我们直接使用pthread_create即可

OS层是没有线程的概念的,只有轻量级进程的概念,线程的概念是线程库维护的

线程的tid就是线程控制块的在块当中的起始地址

另外除了主线程,所有其他线程的独立栈,都在共享区具体来讲是在pthread库中,tid指向的用户tcb(线程控制块)中。

保证了多线程调度了,不会互相干扰。

测试独立栈

下面的多线程代码,虽然调用的都是同一个函数,但是函数栈帧在不同的线程栈里面形成,里面的地址都是不同的

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<string>

using namespace std;

#define Num 3

//线程数据
struct thread_data{
    string threadname;
};

void Init(thread_data*td,int i)
{
    td->threadname="thread-"+to_string(i);
}

//转换为16进制
string tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%x",tid);

    return buffer;
}

//线程入口函数,此函数为可重入函数(严格来说,有临界资源)
void* routine(void*arg)
{
    thread_data* td=static_cast<thread_data*> (arg);//获取参数
    int i=0;
    int test_i=0;

    while(i<10)
    {
        cout<<"pid:"<<getpid()<<",name:"<<td->threadname
        <<",tid:"<<tohex(pthread_self())<<",i:"<<i
        <<",test_i:"<<test_i<<",&test_i:"<<tohex((pthread_t)&test_i)<<endl;//测试独立栈
        i++;
        test_i++;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    //保存tid方便后续线程等待
    vector<pthread_t> tids;

    //创建线程
    for(int i=1;i<=Num;i++)
    {
        pthread_t tid;
        thread_data* td=new thread_data();//错误写法thread_data td;td是当前作用域的局部变量,后续会销毁,&td时会出现错误
        Init(td,i);//初始化

        pthread_create(&tid,nullptr,routine,td);

        tids.push_back(tid);
    }

    //线程等待
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }

    return 0;
}

如何在主线程中获新线程的数据?(禁止操作)

定义一个全局指针变量,然后把在新线程的执行函数中,将该全局指针变量指向新线程的数据,即可访问到你需要访问的数据。

线程之间没有秘密,哪是线程的栈上数据,也是可以被其他线程看到并访问的,只不过我们要求线程要有独立栈(而非私有)和上下文数据

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<string>

using namespace std;

#define Num 3

int* get;//全局指针变量

//线程数据
struct thread_data{
    string threadname;
};

void Init(thread_data*td,int i)
{
    td->threadname="thread-"+to_string(i);
}

//转换为16进制
string tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%x",tid);

    return buffer;
}

//线程入口函数,此函数为可重入函数
void* routine(void*arg)
{
    thread_data* td=static_cast<thread_data*> (arg);//获取参数
    int i=0;
    int test_i=0;
    //线程判断
    if(td->threadname=="thread-2")
    {
        get=&test_i;
    }

    while(i<10)
    {
        test_i++;
        cout<<"pid:"<<getpid()<<",name:"<<td->threadname
        <<",tid:"<<tohex(pthread_self())<<",i:"<<i
        <<",test_i:"<<test_i<<",&test_i:"<<tohex((pthread_t)&test_i)<<endl;//测试独立栈
        i++;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    //保存tid方便后续线程等待
    vector<pthread_t> tids;

    //创建线程
    for(int i=1;i<=Num;i++)
    {
        pthread_t tid;
        thread_data* td=new thread_data();//错误写法thread_data td;td是当前作用域的局部变量,后续会销毁,&td时会出现错误
        Init(td,i);//初始化

        pthread_create(&tid,nullptr,routine,td);

        tids.push_back(tid);
    }

    sleep(1);//确保线程创建完毕
    cout<<"main pthread;test_i:"<<*get<<",&test_i:"<<tohex((pthread_t)get)<<endl;

    //线程等待
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }

    return 0;
}

线程局部存储

全局变量是被所有线程看到并访问的

这样操作有没有什么问题呢?

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<string>

using namespace std;

#define Num 3

int g_val=0;

//线程数据
struct thread_data{
    string threadname;
};

void Init(thread_data*td,int i)
{
    td->threadname="thread-"+to_string(i);
}

//转换为16进制
string tohex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%x",tid);

    return buffer;
}

//线程入口函数,此函数为可重入函数
void* routine(void*arg)
{
    thread_data* td=static_cast<thread_data*> (arg);//获取参数
    int i=0;

    while(i<10)
    {
        cout<<"pid:"<<getpid()<<",name:"<<td->threadname
        <<",tid:"<<tohex(pthread_self())<<",i:"<<i
        <<"g_val:"<<g_val<<",&g_val:"<<&g_val<<endl;
        i++,g_val++;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    //保存tid方便后续线程等待
    vector<pthread_t> tids;

    //创建线程
    for(int i=1;i<=Num;i++)
    {
        pthread_t tid;
        thread_data* td=new thread_data();//错误写法thread_data td;td是当前作用域的局部变量,后续会销毁,&td时会出现错误
        Init(td,i);//初始化

        pthread_create(&tid,nullptr,routine,td);

        tids.push_back(tid);
    }

    //线程等待
    for(int i=0;i<tids.size();i++)
    {
        pthread_join(tids[i],nullptr);
    }

    return 0;
}

 共享资源

在上述代码演示中,里面的全局变量g_val就是共享资源,能够被多个线程同时看到并访问

怎么给线程分配私有的全局变量呢?(线程局部存储)

__thread 变量,线程的局部存储,__thread是一个编译选项,注意事项:__thread只能用来定义内置类型,也认为线程存储只能用来定义内置类型,不能用来修饰自定义类型。。

还有一个细节,通过地址对比我们也可以看出,上面的全局变量是在全局变量区,而下面的“全局变量”是在堆栈之间

线程局部存储的作用是什么?

举例:减少系统接口的调用

线程不是有独立栈嘛,为什么不直接使用一个局部变量保存?

线程入口函数里嵌套函数呢?传参吗?

因此我们利用__thread,在线程局部存储里定义了一个线程级别的全局变量,无论嵌套多少层都可以直接调用,和别人互不干扰

tid与LWP

Linux中的线程是用户级线程,并不是内核级进程

LWP是操作系统管理底层轻量级进程(给操作系统看)

tid是线程库中管理线程对应的线程控制块起始虚拟地址(给用户看)

LWP:tid=1:1

用户级执行流:内核lwp=1:1

每一个执行流的本质就是一条调用链,线程的独立栈的存在,就是保障调用链不被其他执行流打扰

当然了,不同的执行流在堆区申请的数据,其他线程都能想办法看到,因为堆区是共享的,但是我们一般不会这么做

  • 29
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java三个线程交替打印可以使用经典的信号量机来实现。首先,我们需要创建一个共享的信号量对象并初始化为1,用来保证三个线程的顺序执行。然后,我们创建一个打印任务类,实现Runnable接口,并重写run方法。在run方法中,我们使用信号量进行线程的控。 具体的实现步骤如下: 1. 创建一个信号量对象,并初始化为1,用来控线程的顺序执行。 Semaphore semaphore = new Semaphore(1); 2. 创建一个打印任务类,实现Runnable接口,并重写run方法。 class PrintTask implements Runnable { private String message; private Semaphore current; private Semaphore next; public PrintTask(String message, Semaphore current, Semaphore next) { this.message = message; this.current = current; this.next = next; } public void run() { try { for (int i = 0; i < 10; i++) { // 获取当前信号量的许可 current.acquire(); // 打印当前信息 System.out.print(message); // 释放下一个信号量的许可 next.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } } 3. 创建三个打印任务对象,并指定当前信号量和下一个信号量的顺序。 Semaphore semaphoreA = new Semaphore(1); Semaphore semaphoreB = new Semaphore(0); Semaphore semaphoreC = new Semaphore(0); Runnable printTaskA = new PrintTask("A", semaphoreA, semaphoreB); Runnable printTaskB = new PrintTask("B", semaphoreB, semaphoreC); Runnable printTaskC = new PrintTask("C", semaphoreC, semaphoreA); 4. 创建三个线程,并分别启动这三个线程。 Thread threadA = new Thread(printTaskA); Thread threadB = new Thread(printTaskB); Thread threadC = new Thread(printTaskC); threadA.start(); threadB.start(); threadC.start(); 通过上述步骤,我们创建了三个信号量和三个打印任务,实现了三个线程的交替打印。其中,Semaphore类的acquire方法用于获取一个许可,如果没有许可则阻塞,release方法用于释放一个许可。通过不同的信号量控线程的顺序执行,从而实现了线程的交替打印。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值