漫话linux:线程特性,分离与并发

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

1.首先是进程与线程的关系,有四种(类似排列组合):单进程单线程,单进程多线程,多个单线程进程,多个多线程进程

2.之前的单线程就是只有一个线程的进程

3.线程ID及地址空间布局:

        1.pthread_creat函数会产生一个线程 ID,存放在第一个参数指向的地址中。 该线程 ID 和前面说的线程 ID 不是一回事

        2.前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程

        3.pthread_creat函数第一个参数指向一个虚拟内存单元,该内存单元的地址即 为新创建线程的线程 ID,属于 NPTL 线程库的范畴。线程库的后续操作,就是根据 该线程 ID 来操作线程的

        4.线程库 NPTL 提供了 pthread_ self 函数,可以获得线程自身的 ID:pthread_t pthread_self(void)

4.pthread_t的类型取决于实现,linux实现的是NPTL,pthread_t类型的线程ID,本质上就是进程地址空间上的一个地址

在线程栈中实现批量传参数据:vector+循环+创建

void *threadRoutine(void *args)//接收类指针
{
    threadData *td=static_cast<threadData *>(args);
    int i=0;
    while(i<10)
    {
        cout<<"pid: "<<getpid()<<" , ";
    }
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        threadData *td=new threadData;
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);
 
    }
}

代码解析:

threadRountine函数:首先参数是一个常规的void类型,用于常规的线程转换,首先把指针类型强转为threadData类型,并且打印10条信息,主函数先创建一个vector,在循环中再定义一个对象,再创建线程并加入vector

防止两个栈空间混乱

所以 要 传指针,指向的是堆空间,一线程一个

下面函数的初始化是如何实现的呢, InitThreadData 的设计

struct threadData
{
    string threadname;
};
 
// __thread threadData td;
 
string toHex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}
 
void InitThreadData(threadData *td, int number)
{
    td->threadname = "thread-" + to_string(number); // thread-0
}

代码解析:首先是存储线程名字的结构体threadData,toHex函数接收一个线程ID,提供一个字符数组负责接收线程ID,函数InitThreadData就是初始化结构体

监测打印

while :; do ps ajx | head -1 && ps ajx | grep myprocess |grep -x grep;sleep 1;done
//将ps ajx替换为ps -aL

所有线程都执行这个函数,但是每个线程都有自己的栈,有独立的线程地址空间,但是虽然是多执行流和独立栈结构,但是可重入函数的出现导致线程的内部数据可以被其他线程所访问,且全局变量可以被所有线程访问和使用

 cout << "pid: " << getpid() << ", tid : "
        //     << toHex(number) << ", threadname: " << td->threadname
        //         << ", g_val: " << g_val << " ,&g_val: " << &g_val <<endl;

g_val 数据称为共享资源

如何给线程一个私有的全局变量:__thread(编译选项创建) int g_val 线程的局部存储,每个线程都对共享资源存储了一份

定义出线程级别的全局变量,并且互不干扰,进行线程的局部存储!只能定义内置类型,可以用于线程函数

5.分离线程

默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏

如果不关心线程的返回值,join是一种负担,这个时候可以告诉系统,当线程退出时,自动释放线程资源。 int pthread_detach(pthread_t thread)

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离: pthread_detach(pthread_self());

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

//测试发现矛盾
// void *threadRoutine(void *args)
// {
//     pthread_detach(pthread_self());//自己分家
 }
int main(){
    vector<pthread_t> tids;
    // for(auto i : tids)
    // {
    //     pthread_detach(i);//被父亲驱逐分家
    // }
    // cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl;
 
    for (int i = 0; i < tids.size(); i++)
    {
        int n = pthread_join(tids[i], nullptr);
        printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
    }

代码解析:在函数中分离当前进程(self形参),自动释放而不需要被主线程回收,main函数,首先是一个存储线程ID的vector,然后把vector里面的线程ID取出来驱逐出去,并进行打印

创建多个进程

  vector<pthread_t> tids;//循环创建
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData;
        InitThreadData(td, i);
 
        pthread_create(&tid, nullptr, threadRoutine, td);
        tids.push_back(tid);
        //sleep(1);
    }

6.线程并发

临界资源:多线程执行流共享的资源就叫做临界资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源, 通常对临界资源起保护作用

原子性:(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有 两态,要么完成,要么未完成

互斥量 mutex:

        1.大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量

        2.但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享

完成线程间的交互,但是多个线程之间使用一个变量可能会导致一些问题

模拟抢电影票

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
 
using namespace std;
 
#define NUM 4
 
class threadData
{
public:
    threadData(int number)
    {
        threadname = "thread-" + to_string(number);
    }
 
public:
    string threadname;
};
 
int tickets = 1000; // 用多线程,模拟一轮抢票
 
void *getTicket(void *args)
{
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->threadname.c_str();
    while (true)
    {
        if(tickets > 0)
        {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets); // ?
            tickets--;
        }//有票的时候才进行抢票
        else
            break;
    }
    printf("%s ... quit\n", name);
    return nullptr;
}
 
int main()
{
    vector<pthread_t> tids//存储多线程的id信息
    vector<threadData *> thread_datas;//传入的参数 类 数组
    for (int i = 1; i <= NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData(i);//创建对象传入
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);
        tids.push_back(tid);
    }
    //四个线程同时运行抢票

代码解析,首先是结构体ThreadData,存储线程名和初始化,然后是全局变量tickets模拟电影票数,然后是getticket函数,传入一个线程数据,强转后提取线程名字,然后是一个死循环,如果有票就抢,没有就终止,线程结束后再打印名字,然后是主函数,存储多线程的id信息,以及传参数组,再创建4个线程同时抢票

对象数组的创建和传入,传入类 类型,因为类具有可扩展性

vector<threadData *> thread_datas;
threadData *td = new threadData(i);//创建对象传入
thread_datas.push_back(td);//对象存储到数组空间中
pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);//传入

两个vector的回收

    for (auto thread : tids)
    {
        pthread_join(thread, nullptr);
    }
 
    for (auto td : thread_datas)
    {
        delete td;
    }
    return 0;
}

运行发现,存在抢票抢超了,共享数据-->数据不一致问题! 肯定和多线程并发访问是有关系的

首先需要来理解一下 tickets--   为什么不是一个原子操作

操作并不是原子的,而是对应三条汇编指令,其中在执行任何一条指令时都有可能被切走

load:将共享变量tickets从内存加载到寄存器中

update:更新寄存器里面的值,执行-1操作

store:将新值,从寄存器写回共享变量ticket的内存地址

寄存器不等于寄存器的内容,线程在执行的时候,将共享数据,加载到 CPU 寄存器的本质:把数据的内容,变成了自己的上下文,同时自己拷贝了一份数据

拿走数据,拿走上下文,每次通过上下文轮番刷新

对一个全局变量进行多线程并发--/++是否是安全的?(并发情况下,对变量的操作)

不安全

出现负数原因分析 sum

 while (true)
    {
        if(tickets > 0)
        {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets); // ?
            tickets--;
        }//有票的时候才进行抢票

if语句判断条件为真以后(判断也需要CPU参与),代码可以并发的切换到其他线程,"同一时刻"有多个线程判断tickets时tickets的值是相同的

usleep用于模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段

tickets-- 操作本身就不是一个原子操作

每个进程都认为自己是 1,操作完第一步之后就被切走了,我在修改的时候你也修改了,例如:

假设我们有两个线程,它们都在尝试递减全局变量 tickets 的值。如果没有适当的同步机制,可能会发生以下情形:

1.A和B都检测票数是否大于0

2.A和B都发现票数大于0,递减

3.A和B递减了,但初始数据相同,导致减少的数量为1而不是2

这就是并发的问题,对共享数据的任何访问,保证任何时候只有一个执行流访问!就需要互斥,加锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值