从新理解 ,什么是线程 什么是进程了。
什么是线程
在一个程序里的一个执行路线就叫做线程(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会直接给进程发送信号。进程死了,空间没了,所有的线程自然死了。
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 线程可以调用pthread_ exit终止自己。
- 一个线程可以调用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