linux线程

从新理解 ,什么是线程 什么是进程了。

什么是线程

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是"一个进程内部的控制序列"

一切进程至少都有一个执行线程

线程在进程内部运行,本质是在进程地址空间内运行

在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化

透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

多个task_struct 共享同一份虚拟内存。同时页表也没有这么简单,我们看看上面页表的属性

虚拟地址到物理地址的映射需要一个属性,权限 ,读写权限 ,是否命中等等这里就可以解释为什么我们有一个代码

cpp 复制代码
string* s ="hello world";
*s = "111";

这里其页表有一个读写改的权限,我们的字符串保存在全局区,就是字面量。但是这里的地址时允修改的。所以当发生这样的的操作时 ,CPU的mmu报错。发送信号。

创建进程的接口

cpp 复制代码
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

*pthread_t thread

这是一个指针,指向 pthread_t 类型的变量,该变量在成功创建线程后会被更新,以包含新线程的标识符。你可以使用这个标识符来引用新线程,例如在调用 pthread_join 等函数时。

*const pthread_attr_t attr

这是一个指向 pthread_attr_t 结构的指针,该结构用于定义线程的属性。如果传入 NULL,线程将使用默认属性创建。线程属性可以包括线程的堆栈大小、调度策略、分离状态等。

***void (start_routine) (void )

这是一个函数指针,指向线程将要执行的函数。这个函数必须接受一个 void * 类型的参数,并返回一个 void * 类型的值。这个参数允许你传递一个指向任何数据类型的指针给新线程,使得新线程可以访问和操作这些数据。

*void arg

这是一个 void * 类型的指针,指向传递给 start_routine 函数的参数。你可以通过这个指针传递结构体、数组或任何其他类型的指针,只要它对于你的应用来说是有意义的。

我想们先看一个简单的代码

cpp 复制代码
#include <pthread.h>
#include <iostream>
#include<stdio.h>
#include<string.h>
#include<string>
#include <unistd.h>
using namespace std;
// *pthread_t thread

void* rout(void* arg)
{
    char* threadName = static_cast<char*>(arg);
    cout<<threadName<<endl;
    while(1)
    {
        cout<<threadName<<endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,rout,(void*)"zk创建的线程1");
    char buffer[128];
    if(ret!=0)
    {
        snprintf(buffer,sizeof buffer , "曾奎创建的线程",1,"出错了");
    }

    while(1)
    {
        
        cout<<"主线程正在执行"<<endl;
        sleep(1);
    }

    return 0;
}

结果

bash 复制代码
[zk@VM-24-17-centos lesson26]$ ./mythread 
主线程正在执行
zk创建的线程1
zk创建的线程1
主线程正在执行
zk创建的线程1
主线程正在执行
zk创建的线程1
主线程正在执行
zk创建的线程1
zk创建的线程1
主线程正在执行

同时我们查看线程的创建情况

bash 复制代码
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
zk        1113 24465  1113  0    2 09:40 pts/4    00:00:00 ./mythread
zk        1113 24465  1114  0    2 09:40 pts/4    00:00:00 ./mythread

这里我们发现 , 线程创建pid是一样的,正好也跟我们之前的想法一样,但是这里有一个lwp ,才是区分不同轻量级线程的关键字。PID一样证明区访问的同一片虚拟地址空间 ,lwp是给cpu执行的轻量级单元。轻量级进程ID,对于线程来说,这个和线程ID是相同的。证明我们确实创建了两个线程。

我们对这一段话理解一下,我们这里新加了一个函数,在线程和主线程都是可以访问的。

cpp 复制代码
#include <pthread.h>
#include <iostream>
#include<stdio.h>
#include<string.h>
#include<string>
#include <unistd.h>
using namespace std;
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

void printss()
{
    cout<<111<<endl;
}

void* rout(void* arg)
{
    char* threadName = static_cast<char*>(arg);
    cout<<threadName<<endl;
    while(1)
    {
        printss();
        cout<<threadName<<endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,rout,(void*)"zk创建的线程1");
    char buffer[128];
    if(ret!=0)
    {
        snprintf(buffer,sizeof buffer , "曾奎创建的线程",1,"出错了");
    }

    while(1)
    {
        
        cout<<"主线程正在执行"<<endl;
        sleep(1);
    }

    return 0;
}

线程为什么需要的时间更短?


多核的概念,运算器控制器 ,运算器有很多个。

线程ID及进程地址空间布局

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

查看线程id

bash 复制代码
pthread_t pthread_self(void);

我们要理解这个图

linux操作系统本身是有轻量级进程这个概念的比如接口:

cpp 复制代码
vfork 和 clone

但是这个两个接口不太好用 , 大牛就封装了一个pthread库,其实就是对这些接口的封装。官方也觉得很好,在每一个linux系统里面都下载了这个库。我么可以验证一下

使用命令,我确实没安装,确实就自带了

bash 复制代码
[zk@VM-24-17-centos lesson26]$ locate libpthread
/usr/lib64/libpthread-2.17.so
/usr/lib64/libpthread.a
/usr/lib64/libpthread.so
/usr/lib64/libpthread.so.0
/usr/lib64/libpthread_nonshared.a
[zk@VM-24-17-centos lesson26]$ 

然后动态库会被加载到共享区。这个问题我们后面验证。当我们想用pthread_create创建一个线程的时候,他首先会在共享区生成一个TCB .然后用传TCB的参数,去调用clone接口,然后系统会创建一个pcb出来,这个PCB与TCB是一 一对应的。然后线程也会有西游的属性 ,就保存在共享区的位置。

这是一个demo

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include<string>
#include<unistd.h>
#define NUM 10
using namespace std;
/*
创建10个线程
*/

void* start_routine(void* arg)
{
    string name = static_cast<const char*>(arg);// 一种安全的转换方式
    while(true)
    {
        sleep(1);
        cout << "子线程创建成功,名字是:" << name << ", 线程的id是: 0x" << hex << pthread_self() << dec << endl;
    }
}
int main()
{
    for (int i = 0; i < NUM; i++)
    {
        pthread_t pid;
        //    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
        char namebuffer[64];
        snprintf(namebuffer,sizeof namebuffer,"zk创建的线程%d",i);
        // cout<<namebuffer<<endl;
        pthread_create(&pid, NULL, start_routine, namebuffer);
    }
    while (true)
    {
        
        cout<<"new thread create success,now is main thread: id: "<< hex << pthread_self() << dec << endl;
        sleep(1);
    }
    

    return 0;
}

输出结果

cpp 复制代码
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af424f700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af3a4e700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7f1af224b700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1aeaa4c700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af0a48700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af1a4a700
new thread create success,now is main thread: id: 7f1af526c740
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0xzk创建的线程4, 线程的id是: 0x7f1af424f7007f1af3a4e700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7f1af224b700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1aeaa4c700

子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7f1af1249700子线程创建成功,名字是:
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af1a4a700
子线程创建成功,名字是:zk创建的线程9zk创建的线程9, 线程的id是: 0x, 线程的id是: 0x7f1aebfff700
7f1af0a48700
new thread create success,now is main thread: id: 7f1af526c740
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af424f700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7f1af224b700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1aeaa4c700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af3a4e700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0xzk创建的线程8, 线程的id是: 0x7f1af1a4a7007f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af0a48700
new thread create success,now is main thread: id: 7f1af526c740

子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4zk创建的线程6, 线程的id是: 0x, 线程的id是: 0x7f1af224b7007f1aeaa4c700

子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af3a4e700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af424f700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af1a4a700
new thread create success,now is main thread: id: 7f1af526c740
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af0a48700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4zk创建的线程4, 线程的id是: 0x, 线程的id是: 0x7f1af3a4e7007f1af424f700

子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7f1af224b700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1aeaa4c700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af1a4a700
new thread create success,now is main thread: id: 7f1af526c740
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af0a48700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0xzk创建的线程6, 线程的id是: 0x7f1af3a4e7007f1af224b700

子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0xzk创建的线程47f1aeaa4c700, 线程的id是: 0x
7f1af424f700子线程创建成功,名字是:
zk创建的线程4, 线程的id是: 0x7f1af324d700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af1a4a700
new thread create success,now is main thread: id: 7f1af526c740
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af0a48700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:zk创建的线程4子线程创建成功,名字是:, 线程的id是: 0xzk创建的线程4, 线程的id是: 0x7f1af3a4e7007f1af424f700

子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7f1af224b700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1aeaa4c700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
new thread create success,now is main thread: id: 子线程创建成功,名字是:7f1af526c740
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af1a4a700zk创建的线程9, 线程的id是: 0x7f1af0a48700
子线程创建成功,名字是:
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
zk创建的线程8, 线程的id是: 0x7f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0xzk创建的线程4, 线程的id是: 0x7f1af3a4e7007f1af424f700

子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7f1af224b700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1aeaa4c700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7f1af324d700
new thread create success,now is main thread: id: 7f1af526c740
子线程创建成功,名字是:zk创建的线程9子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7f1af1249700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1aebfff700
子线程创建成功,名字是:, 线程的id是: 0x7f1af1a4a700
zk创建的线程9, 线程的id是: 0x7f1af0a48700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7f1af2a4c700

查看线程创建这里发现LWP连续的 ,想到了数组 ,确实在pthread内部是以数组方式管理这种结构。

bash 复制代码
[zk@VM-24-17-centos ~]$ ps -eLf | grep test
zk       23026 24465 23026  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23027  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23028  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23029  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23030  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23031  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23032  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23033  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23034  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23035  0   11 10:47 pts/4    00:00:00 ./test
zk       23026 24465 23036  0   11 10:47 pts/4    00:00:00 ./test
zk       23176 32508 23176  0    1 10:47 pts/5    00:00:00 grep --color=auto test

上面结果存在一个问题 ,我们发现只有少数几个线程在跑 ,其余的们都没有跑,为什么呢,没创建成功嘛?并不是,创建出来的线程和主线程的执是由系统分配的了。没有规定谁先后。但是子线程我在写回调函数的时候,加了一个小心机。先sleep1秒这里就很有可能会触发cpu的时间片轮转,然后切换下一个进程。然后接着执行主进程。这里的namebuffer是一个地址。我们在子进程里面调用的也是地址。小心。我在主进程会把接着执行snprintf ,对地址的数据从新改变,就造成了上诉的局面。

怎么办

查看代码

可以将id 和信息封装为一个类

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <string>
#include <unistd.h>
#define NUM 10
using namespace std;
/*
创建10个线程
*/

class ThreadData
{
public:
    pthread_t _tid;
    char _namebuffer[64];
};
void *start_routine(void *arg)
{
    ThreadData* data = static_cast<ThreadData*>(arg); // 一种安全的转换方式
    while (true)
    {
        sleep(1);
        cout << "子线程创建成功,名字是:" <<data->_namebuffer  << ", 线程的id是: 0x" << hex << pthread_self() << dec << endl;
    }
    return nullptr;
}
int main()
{
    for (int i = 0; i < NUM; i++)
    {
        ThreadData* thread_data[NUM];
        // pthread_t pid;
        //    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
        // char namebuffer[64];
        ThreadData* cnt = new ThreadData();
        snprintf(cnt->_namebuffer, sizeof cnt->_namebuffer, "zk创建的线程%d", i);
        // cnt->_namebuffer =namebuffer;
        // cout<<namebuffer<<endl;
        pthread_create(&cnt->_tid, NULL, start_routine, cnt);
    }
    while (true)
    {

        cout << "new thread create success,now is main thread: id: " << hex << pthread_self() << dec << endl;
        sleep(1);
    }

    return 0;
}

运行结果

bash 复制代码
[zk@VM-24-17-centos lesson27]$ ./test 
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x7fb6af669700
子线程创建成功,名字是:zk创建的线程2, 线程的id是: 0x7fb6aee68700
子线程创建成功,名字是:zk创建的线程3, 线程的id是: 0x7fb6ae667700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700
子线程创建成功,名字是:zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7fb6ace64700
子线程创建成功,名字是:zk创建的线程7, 线程的id是: 0x7fb6ac663700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7fb6abe62700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7fb6ab661700
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程2, 线程的id是: 0xzk创建的线程3, 线程的id是: 0x7fb6aee687007fb6ae667700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x7fb6ab661700
子线程创建成功,名字是:zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x子线程创建成功,名字是:7fb6af669700
zk创建的线程8子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700
子线程创建成功,名字是:zk创建的线程7, 线程的id是: 0x7fb6ac663700
, 线程的id是: 0x7fb6abe62700zk创建的线程6, 线程的id是: 0x7fb6ace64700


new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程2, 线程的id是: 0xzk创建的线程9, 线程的id是: 0x7fb6aee687007fb6ab661700
子线程创建成功,名字是:zk创建的线程7, 线程的id是: 0x7fb6ac663700

子线程创建成功,名字是:zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7fb6ace64700
子线程创建成功,名字是:zk创建的线程3, 线程的id是: 0x7fb6ae667700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7fb6abe62700
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x7fb6af669700
子线程创建成功,名字是:zk创建的线程2, 线程的id是: 0x7fb6aee68700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7fb6ace64700子线程创建成功,名字是:zk创建的线程3, 线程的id是: 0x7fb6ae667700
子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7fb6abe62700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程7, 线程的id是: 0x7fb6ac663700
子线程创建成功,名字是:
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700
zk创建的线程9, 线程的id是: 0x7fb6ab661700
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x7fb6af669700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程3, 线程的id是: 0xzk创建的线程7, 线程的id是: 0x7fb6ae6677007fb6ac663700
子线程创建成功,名字是:zk创建的线程9, 线程的id是: 0x
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程8, 线程的id是: 0x7fb6abe62700zk创建的线程6
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700
7fb6ab661700
, 线程的id是: 0x7fb6ace64700
zk创建的线程2, 线程的id是: 0x7fb6aee68700
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x7fb6af669700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程7, 线程的id是: 0xzk创建的线程8, 线程的id是: 0x7fb6ac6637007fb6abe62700
子线程创建成功,名字是:zk创建的线程2, 线程的id是: 0x
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700zk创建的线程9, 线程的id是: 0x7fb6ab661700
子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7fb6ace64700

子线程创建成功,名字是:zk创建的线程3, 线程的id是: 0x7fb6ae667700
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x7fb6af669700
7fb6aee68700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程9zk创建的线程7, 线程的id是: 0x, 线程的id是: 0x7fb6ab6617007fb6ac663700

子线程创建成功,名字是:zk创建的线程6, 线程的id是: 0x7fb6ace64700
子线程创建成功,名字是:子线程创建成功,名字是:zk创建的线程3, 线程的id是: 0x7fb6ae667700zk创建的线程8
, 线程的id是: 0x7fb6abe62700
子线程创建成功,名字是:zk创建的线程4, 线程的id是: 0x7fb6ade66700
new thread create success,now is main thread: id: 7fb6b0e87740
子线程创建成功,名字是:zk创建的线程5, 线程的id是: 0x7fb6ad665700
子线程创建成功,名字是:zk创建的线程0, 线程的id是: 0x7fb6afe6a700
子线程创建成功,名字是:zk创建的线程1, 线程的id是: 0x7fb6af669700
子线程创建成功,名字是:zk创建的线程2, 线程的id是: 0x7fb6aee68700

打印出来的地址是什么?我们在看这个图

打印出来的值其实是这个线程在共享区的起始地址?我们怎么证明

定义一个全局变量 , 我们先不对其进行任何操作,这是所有人都可以访问的公共资源。他的数据存放在全局区

代码展示

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include <string>
#include<unistd.h>
using namespace std;
// 定义一个全局变量
int globeNum = 100;

void* start(void* arg)
{
    string s  = static_cast<const char*>(arg);
    while(true)
    {
        sleep(1);
        cout<<"子线程: "<<globeNum<<"地址在:"<<&globeNum<<endl;
        globeNum--;
    }

}
int main()
{

    // 我们创建一个线程
    pthread_t tid;
    pthread_create(&tid,NULL,start,(void*)"zk创建的进程");
    while(true)
    {
        sleep(1);
        cout<<"主线程: "<<globeNum<<"地址在:"<<&globeNum<<endl;
        globeNum--;
    }
    return 0;
}

结果

bash 复制代码
[zk@VM-24-17-centos lesson28]$ ./test 
主线程: 100地址在:0x6020b4
子线程: 99地址在:0x6020b4
子线程: 主线程: 9898地址在:地址在:0x6020b40x6020b4

主线程: 子线程: 96地址在:96地址在:0x6020b40x6020b4

子线程: 主线程: 0x5e地址在:0x5e地址在:0x6020b40x6020b4

子线程: 主线程: 0x5c0x5c地址在:地址在:0x6020b40x6020b4

子线程: 主线程: 0x5a地址在:0x5a地址在:0x6020b40x6020b4

子线程: 主线程: 0x58地址在:0x58地址在:0x6020b40x6020b4

主线程: 子线程: 0x56地址在:0x56地址在:0x6020b40x6020b4

主线程: 子线程: 0x540x54地址在:地址在:0x6020b40x6020b4

主线程: 0x52地址在:0x6020b4
子线程: 0x52地址在:0x6020b4
主线程: 0x50地址在:0x6020b4
子线程: 0x4f地址在:0x6020b4

地址都一样 ,注意地址的大小 。我们知道全局区就在内存的下端,地址应该是比较小的 。

接下来看看这个代码

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include <string>
#include<unistd.h>
using namespace std;
// 定义一个全局变量
thread_local int globeNum = 100;

void* start(void* arg)
{
    string s  = static_cast<const char*>(arg);
    while(true)
    {
        sleep(1);
        cout<<"子线程: "<<globeNum<<"地址在:"<<&globeNum<<endl;
        globeNum--;
    }

}
int main()
{

    // 我们创建一个线程
    pthread_t tid;
    pthread_create(&tid,NULL,start,(void*)"zk创建的进程");
    while(true)
    {
        sleep(1);
        cout<<"主线程: "<<globeNum<<"地址在:"<<&globeNum<<endl;
        globeNum--;
    }
    return 0;
}

结果

cpp 复制代码
[zk@VM-24-17-centos lesson28]$ ./test 
100
主线程: 地址在:0x7f86da75f77c
100
子线程: 地址在:0x7f86d97426fc
9999
子线程: 地址在:0x7f86d97426fc

主线程: 地址在:0x7f86da75f77c
9898
主线程: 地址在:0x7f86da75f77c

子线程: 地址在:0x7f86d97426fc
9797
子线程: 地址在:0x7f86d97426fc

主线程: 地址在:0x7f86da75f77c
9696
子线程: 地址在:0x7f86d97426fc

主线程: 地址在:0x7f86da75f77c

首先发现地址不一样 ,地址变大。共享区在上面 ,并且值分开了 ,这个就相当于这个全局变量每一个线程单独存在一份。

线程如何终止?这里用exit不行 ,线程用exit会直接给进程发送信号。进程死了,空间没了,所有的线程自然死了。

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

    需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

demo 代码

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
// 定义一个全局变量
thread_local int globeNum = 100;

void *start(void *arg)
{
    string s = static_cast<const char *>(arg);
    while (true)
    {
        sleep(1);
        cout << globeNum << endl;
        cout << "子线程: "  << "地址在:" << &globeNum << endl;
        globeNum--;
    }
    return pthread_exit((void*)0); 
}
int main()
{
    // 我们创建一个线程
    pthread_t tid;
    pthread_create(&tid, NULL, start, (void *)"zk创建的进程");
    while (true)
    {
        sleep(1);
        cout << globeNum << endl;
        cout << "主线程: " << "地址在:" << &globeNum << endl;
        globeNum--;
    }
    pthread_join(tid, NULL); // 等待子线程结束
    pthread_exit(NULL); // 主线程也优雅地退出,避免终止其他可能的线程
    return 0;
}

pthread_cancel函数

功能:取消一个执行中的线程

原型

int pthread_cancel(pthread_t thread);

参数

thread:线程ID

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


分离线程

cpp 复制代码
int pthread_detach(pthread_t thread);

joinable和分离是冲突的,一个线程不能既是是joinable又是分离的。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run( void * arg )
{
    pthread_detach(pthread_self());
    printf("%s\n", (char*)arg);
    return NULL;
}
int main( void )
{
    pthread_t tid;
    if ( pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") != 0 ) {
        printf("create thread error\n");
        return 1;
    }
    int ret = 0;
    sleep(1);//很重要,要让线程先分离,再等待
    if ( pthread_join(tid, NULL ) == 0 ) {
        printf("pthread wait success\n");
        ret = 0;
    } else {
        printf("pthread wait failed\n");
        ret = 1;
    }
    return ret;
}

结果

bash 复制代码
[zk@VM-24-17-centos lesson29]$ ./test 
thread1 run...
pthread wait failed

获取返回信息(大话题)

以下是原理

我们好好讲一讲原理!

首先我们看我们的函数。

这是我们的线程函数

cpp 复制代码
void *start(void *arg)
{
    string s = static_cast<const char *>(arg);
    while (true)
    {
        sleep(1);
        cout << globeNum << endl;
        cout << "子线程: "  << "地址在:" << &globeNum << endl;
        globeNum--;
    }
    return pthread_exit((void*)0); 
}

重要理解

返回值是void 。但是我们pthread_jion的参数是void* 。是一个输入型参数。但是我就是一直有疑惑 , 为什么不直接找一个void* 的直接赋值过去呢?为什么为什么。我不懂。后强行想了一个解释。这个函数的接口就是void** ,我们要满足这个规范。**

C++语言的线程

cpp 复制代码
#include <iostream>
#include<thread>
#include<unistd.h>

void thread_run()
{
    while(true)
    {
        std::cout<<"我是新线程。。。"<<std::endl;
        sleep(1);
    }
}
int main()
{
    std::thread t1(thread_run);

    while(true)
    {
        sleep(1);
        std::cout<<"我是主线程。。。"<<std::endl;
    }

    t1.join();
    return 0;
}

结果:

cpp 复制代码
[zk@VM-24-17-centos lesson30]$ ./test 
我是新线程。。。
我是主线程。。。
我是新线程。。。
我是主线程。。。我是新线程。。。

我是主线程。。。
我是新线程。。。
我是主线程。。。我是新线程。。

但是这里我们使坏,我们在makefile的时候把外部链接的把pthread去掉。

cpp 复制代码
[zk@VM-24-17-centos lesson30]$ ./test 
terminate called after throwing an instance of 'std::system_error'
  what():  Enable multithreading to use std::thread: Operation not permitted
Aborted

调用pthread------create找不到

关键字:__thread

自己封装的线程

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<pthread.h>
#include <stdio.h>
#include<unistd.h>
#include <functional>
#include<cassert>

// 首先我们需要什么 , 名字 ,id ,需要的执行的函数, 还有线程属性可以包括线程的堆栈大小、调度策略、分离状态等。

class Thread;

//上下文,当成一个大号的结构体
class Context
{
public:
    Thread *_this;
    void *_args;
public:
    Context():_this(nullptr), _args(nullptr)
    {}
    ~Context()
    {}
};


class Thread
{
public:
    // 这里就是申明了一种函数类型
    typedef std::function<void*(void*)> func_t;
    const int num = 1024;
public:
    Thread(func_t func, void *args = nullptr, int number = 0)
    :_func(func)
    ,_args(args)
    {
        char buffer[num];
        snprintf(buffer, sizeof buffer,"thread-%d",number);
        _name = buffer;

        Context* ctx = new Context();
        ctx->_this=this;
        ctx->_args =_args;

        int n = pthread_create(&_tid, nullptr, start_routine, ctx);
        assert(n == 0); //编译debug的方式发布的时候存在,release方式发布,assert就不存在了,n就是一个定义了,但是没有被使用的变量
        // 在有些编译器下会有warning
        (void)n;
    }

    static void* start_routine(void*args)
    {
        Context *ctx = static_cast<Context *>(args);
        void *ret = ctx->_this->run(ctx->_args);
        delete ctx;
        return ret;
    }

    void join()
    {
        int n = pthread_join(_tid, nullptr);
        assert(n == 0);
        (void)n;
    }

    void *run(void *args)
    {
        return _func(args);
    }

    ~Thread()
    {
        // do nothing
    }
private:
    std::string _name; // 线程名字
    pthread_t _tid;   // 线程id
    void* _args; // 这个其实是pthread_create 函数的const pthread_attr_t attr属性 。
                 // 如果传入 NULL,线程将使用默认属性创建。线程属性可以包括线程的堆栈大小、调度策略、分离状态等。
    func_t _func; // 执行的任务函数
};

测试代码

cpp 复制代码
#include"Thread.hpp"

int g_val =100;

std::string changeId(const pthread_t &thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}
void *start_routine(void *args)
{
    std::string threadname = static_cast<const char *>(args);
    // pthread_detach(pthread_self()); //设置自己为分离状态

    int cnt = 5;
    while (true)
    {
        std::cout << threadname << " running ... : " << changeId(pthread_self()) <<" g_val: "<< g_val << " &g_val: " << &g_val << std::endl;
        sleep(1);
        g_val++;
    }

    return nullptr;
}
int main()
{
    // #define NUM 4
    //     pthread_mutex_t lock;
    //     pthread_mutex_init(&lock, nullptr);
    //     std::vector<pthread_t> tids(NUM);
    //     for(int i = 0; i < NUM; i++)
    //     {
    //         char buffer[64];
    //         snprintf(buffer, sizeof(buffer), "thread %d", i+1);
    //         ThreadData *td = new ThreadData(buffer, &lock);
    //         pthread_create(&tids[i], nullptr, getTicket, td);
    //     }

    //     for(const auto &tid: tids)
    //     {
    //         pthread_join(tid, nullptr);
    //     }



    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, start_routine, (void *)"thread 1");
    pthread_create(&t2, nullptr, start_routine, (void *)"thread 2");
    pthread_create(&t3, nullptr, start_routine, (void *)"thread 3");
    pthread_create(&t4, nullptr, start_routine, (void *)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    
    return 0;
}

做一个抢票的

代码如图所示

cpp 复制代码
#include"Thread.hpp"

int g_val =100;
int ticket =10000;

std::string changeId(const pthread_t &thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}
void *start_routine(void *args)
{
    std::string threadname = static_cast<const char *>(args);
    // pthread_detach(pthread_self()); //设置自己为分离状态

    int cnt = 5;
    while (true)
    {
        std::cout << threadname << " running ... : " << changeId(pthread_self()) <<" g_val: "<< g_val << " &g_val: " << &g_val << std::endl;
        sleep(1);
        g_val++;
    }

    return nullptr;
}


void* getTicket(void* arg)
{
    std::string username = static_cast<const char*>(arg);
    while(true)
    {
        if(ticket>0)
        {
            usleep(1000);
            // 值得抢票
            std::cout<<username<<"正在抢票:"<<ticket<<std::endl;
            ticket--;
        }else{
            break;
        }
    }
    return nullptr;
}
int main()
{
    // #define NUM 4
    //     pthread_mutex_t lock;
    //     pthread_mutex_init(&lock, nullptr);
    //     std::vector<pthread_t> tids(NUM);
    //     for(int i = 0; i < NUM; i++)
    //     {
    //         char buffer[64];
    //         snprintf(buffer, sizeof(buffer), "thread %d", i+1);
    //         ThreadData *td = new ThreadData(buffer, &lock);
    //         pthread_create(&tids[i], nullptr, getTicket, td);
    //     }

    //     for(const auto &tid: tids)
    //     {
    //         pthread_join(tid, nullptr);
    //     }


    
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, getTicket, (void *)"thread 1");
    pthread_create(&t2, nullptr, getTicket, (void *)"thread 2");
    pthread_create(&t3, nullptr, getTicket, (void *)"thread 3");
    pthread_create(&t4, nullptr, getTicket, (void *)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    
    return 0;
}

结果 ,发现抢到-1 -2 出bug 了

bash 复制代码
thread 2正在抢票:12
thread 4正在抢票:11
thread 1正在抢票:10
thread 3正在抢票:9
thread 2正在抢票:8
thread 4正在抢票:7
thread 1正在抢票:6
thread 3正在抢票:6
thread 2正在抢票:4
thread 4正在抢票:3
thread 1正在抢票:2
thread 3正在抢票:2
thread 2正在抢票:0
thread 4正在抢票:-1
thread 1正在抢票:-2

为什么呢?

看一下


看这张图就行了,正常我们执行后置++操作,我们要先把数据加载到寄存器,然后寄存器算逻运算,然后把值返回给把内存。但是上面三部的示意图 ,但是当我之星到第二步的时候 ,我发生时间片轮转。这时候 ,会把所有的上下文信息都一起移走。注意 ,这个时候的ticket还没有进行减法操作 ,值就没有改,另外的进程进来的时候,读取的ticket还是原来的。

这时候我们就需要使同步和互斥来操作了

我们要知道对临界资源的操作。



上锁之后的代码

cpp 复制代码
#include"Thread.hpp"
pthread_mutex_t mutex;
int g_val =100;
int ticket =10000;

std::string changeId(const pthread_t &thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}
void *start_routine(void *args)
{
    std::string threadname = static_cast<const char *>(args);
    // pthread_detach(pthread_self()); //设置自己为分离状态

    int cnt = 5;
    while (true)
    {
        std::cout << threadname << " running ... : " << changeId(pthread_self()) <<" g_val: "<< g_val << " &g_val: " << &g_val << std::endl;
        sleep(1);
        g_val++;
    }

    return nullptr;
}


void* getTicket(void* arg)
{
    std::string username = static_cast<const char*>(arg);
    
    while(true)
    {
        pthread_mutex_lock(&mutex);
        if(ticket>0)
        {
            usleep(1000);
            // 值得抢票
            std::cout<<username<<"正在抢票:"<<ticket<<std::endl;
            ticket--;
            pthread_mutex_unlock(&mutex);
        }else{
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    return nullptr;
}
int main()
{
    pthread_mutex_init(&mutex, NULL);
    // #define NUM 4
    //     pthread_mutex_t lock;
    //     pthread_mutex_init(&lock, nullptr);
    //     std::vector<pthread_t> tids(NUM);
    //     for(int i = 0; i < NUM; i++)
    //     {
    //         char buffer[64];
    //         snprintf(buffer, sizeof(buffer), "thread %d", i+1);
    //         ThreadData *td = new ThreadData(buffer, &lock);
    //         pthread_create(&tids[i], nullptr, getTicket, td);
    //     }

    //     for(const auto &tid: tids)
    //     {
    //         pthread_join(tid, nullptr);
    //     }


    
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, getTicket, (void *)"thread 1");
    pthread_create(&t2, nullptr, getTicket, (void *)"thread 2");
    pthread_create(&t3, nullptr, getTicket, (void *)"thread 3");
    pthread_create(&t4, nullptr, getTicket, (void *)"thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    pthread_mutex_destroy(&mutex);

    
    return 0;
}

结果

bash 复制代码
thread 3正在抢票:16
thread 3正在抢票:15
thread 3正在抢票:14
thread 3正在抢票:13
thread 3正在抢票:12
thread 3正在抢票:11
thread 3正在抢票:10
thread 3正在抢票:9
thread 3正在抢票:8
thread 3正在抢票:7
thread 3正在抢票:6
thread 3正在抢票:5
thread 3正在抢票:4
thread 3正在抢票:3
thread 3正在抢票:2
thread 3正在抢票:1
相关推荐
2401_8574396910 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66611 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索13 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
粤海科技君17 分钟前
如何使用腾讯云GPU云服务器自建一个简单的类似ChatGPT、Kimi的会话机器人
服务器·chatgpt·机器人·腾讯云
芒果披萨19 分钟前
Filter和Listener
java·filter
qq_49244844623 分钟前
Java实现App自动化(Appium Demo)
java
傲骄鹿先生27 分钟前
阿里云centos7.9服务器磁盘挂载,切换服务路径
服务器·阿里云·磁盘
阿华的代码王国32 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
有谁看见我的剑了?1 小时前
Ubuntu 22.04.5 配置vlan子接口和网桥
服务器·网络·ubuntu
2739920291 小时前
Ubuntu20.04 安装build-essential问题
linux