Linux多线程编程

news2025/7/18 3:48:02

文章目录

  • 1、线程基本知识
  • 2、线程控制
  • 3、线程同步与互斥
    • <1>线程互斥
    • <2>线程同步
      • 条件变量
      • 生产者消费者模型
      • POSIX信号量
      • 读者写者问题
    • <3>线程池
    • <4>单例模式

1、线程基本知识

线程概念

线程是在进程内部运行的一个执行分支(执行流),属于进程的一部分,粒度比进程更细和轻量化。
进程内部是指:线程在进程的地址空间内运行。
执行分支:CPU在调度的时候只看PCB,每一个PCB曾经被指派过指向的方法和数据,CPU可以直接调度。
在这里插入图片描述
线程间大部分数据是共享的,部分数据是私有的,如独立栈、上下文、PCB。
线程实现没有专门的PCB,使用进程来模拟的。

线程优点

1、创建一个新线程的代价要比创建一个新进程小得多
2、与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3、线程占用的资源要比进程少很多
4、能充分利用多处理器的可并行数量
5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
6、计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
线程越多越好吗?
不一定如果线程太多会导致线程间被过度调度切换(有成本的)
7、I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
大部分时间是在等待I/O就绪的,线程是不是越多越好?不一定不过I/O允许多一些线程。

线程缺点

1、性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。
2、健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3、缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4、编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

2、线程控制

相关函数

功能:创建一个新的线程
原型
在这里插入图片描述
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

功能:等待线程结束
原型int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

功能:分离线程(可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离)
原型:int pthread_detach(pthread_t thread);
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
返回值:成功返回0;失败返回错误码

功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码

线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1、从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2、线程可以调用pthread_ exit终止自己。
3、一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
pthread_exit函数
原型:void pthread_exit(void *value_ptr);
参数:value_ptr不要指向一个局部变量。(退出码)
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

pthread_ self函数
功能:可以获得线程自身的ID:
原型:pthread_t pthread_self(void);
查看的线程id是pthread库中的id不是linux内核中的LWP,
pthread库的线程id是一个内存地址(虚拟地址)

来段代码感受一下:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *args)
{
    const char *id = (const char*)args;
    for(int i = 0; i < 10; i++)
    {
        printf("我是%s线程, 我的线程ID是%d, 我的进程ID是%d\n", id, pthread_self(), getpid());
        sleep(1);
    }
	  //进程退出三种方式
	  //exit(123); 会同时终止调主进程 
	  //pthread_exit((void*)123);
	  return (void*)123;
}

int main()
{
    pthread_t tid;
    //线程创建
    pthread_create(&tid, NULL, thread_run, (void*)"thread 1");
    
    for(int i = 0; i < 10; i++)
    {
        printf("我是main 线程, 我的ID是%d\n", getpid());
        sleep(1);
        //线程取消
        if(i == 2)
            pthread_cancel(tid);
    }
	printf("wait sub thread....\n");
	sleep(1);
	//指针变量本身就可以充当某种容器保存数据 void* 32/4  64/8 ->linux 下是64位
	void* status = NULL;  //指针变量本身就可以充当某种容器保存数据
	// 等待线程
	int ret = pthread_join(tid[0], &status);
    //等待线程,若不等待则会像进程一样出现僵尸线程的问题
    printf("ret: %d,status:%d \n ",ret, (long int)status);
    return 0;
}

我们将上面代码运行起来,通过ps -aL可以查看线程和进程。
在这里插入图片描述

3、线程同步与互斥

<1>线程互斥

为什么要互斥

主要是因为多个线程并发的操作共享变量,会带来一些问题。我们通过一段抢票代码来观察一下。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;

void *route(void *arg)
{
    char *id = (char*)arg;
    while ( 1 ) 
    {
        if ( ticket > 0 ) 
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--; //这里看起来就是一行C、C++代码,但并非原子的,在汇编级别它是多行代码
        } 
        else 
        {
            break;
        }
    }
}
int main( void )
{
    pthread_t tid[5];
    for(int i = 0; i < 5; i++)
    {
        pthread_create(&tid[i], NULL, route, (void*)"thread 1");
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(tid[i], NULL);
    }
    return 0;
}

运行结果:
在这里插入图片描述
我们看到这里票被抢到了负值,这在现实中是绝对不合理的。也就是出现了线程安全问题。
要解决以上问题,需要做到三点
1、代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
2、如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
3、如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

互斥量mutex接口

初始化互斥量
方法1: 静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法2: 动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL

销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t *mutex);
注意:
使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误码

调用pthread_mutex_ lock 时,可能会遇到以下情况:
1、互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
2、发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_mutex_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

改进上面的售票系统:

#include <iostream>
#include <cstdio>
#include <ctime>
#include <mutex>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>

//tickets是不是就是所谓的临界资源! tickets-- 是原子的吗?(是安全的吗?)
//为了让多个线程进行切换,线程什么时候可能切换(1. 时间片到了 2. 检测的时间点:从内核态返回用户态的时候)
//对临界区进行加锁
class Ticket{
    private:
    int tickets;
    
    //pthread_mutex_t vs std::mutex
    pthread_mutex_t mtx; //原生线程库,系统级别
    // std::mutex mymtx; //C++ 语言级别

public:
    Ticket():tickets(1000)
    {
        pthread_mutex_init(&mtx, nullptr);
    }
    bool GetTicket()
    {
        //static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //也可以用这种方式来初始化
    
        bool res = true;
        //原理:lock,unlock-> 是原子的!!(为甚么?)
        //一行代码是原子的:只有一行汇编的情况
        pthread_mutex_lock(&mtx);
        //mymtx.lock();
        
        //执行这部分代码的执行流就是互斥的,串行执行的!
        if(tickets > 0){
            usleep(1000); //1s == 1000ms 1ms = 1000us
            printf("我是[%u], 我要抢的票是: %d\n", pthread_self(), tickets);
            tickets--;   
            printf("");
            //抢票
        }
        else{
            printf("票已经被抢空了\n");
            res = false;
        }
        pthread_mutex_unlock(&mtx);
        //mymtx.unlock();
        return res;
    }
    ~Ticket()
    {
        pthread_mutex_destroy(&mtx);
    }
};
void *ThreadRoutine(void *args)
{
    Ticket *t = (Ticket*)args;

    //购票的时候,不能出现负数的情况
    // srand((long)time(nullptr));
    while(true)
    {
        if(!t->GetTicket())
        {
            break;
        }
    }
}
int main()
{
    Ticket *t = new Ticket();

    pthread_t tid[5];
    for(int i = 0; i < 5; i++){
        int *id = new int(i);
        pthread_create(tid+i, nullptr, ThreadRoutine, (void*)t);
    }

    for(int i = 0 ; i < 5; i++){
        pthread_join(tid[i], nullptr);
    }
    return 0;
}

代码运行结果:
在这里插入图片描述
通过运行结果我们可以看到,此时线程是安全的。

互斥锁的原理

在这里插入图片描述

可重入 VS 线程安全

1、线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
2、重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

线程安全一定是可重入的,可重入的不一定是线程安全的。

常见线程不安全的情况

1、不保护共享变量的函数
2、函数状态随着被调用,状态发生变化的函数
3、返回指向静态变量指针的函数
4、调用线程不安全函数的函数

常见线程安全的情况

1、每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
2、类或者接口对于线程来说都是原子操作
3、多个线程之间的切换不会导致该接口的执行结果存在二义性

死锁四个必要条件

1、互斥条件:一个资源每次只能被一个执行流使用
2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
3、不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

<2>线程同步

条件变量

创建
pthread_cond_t 变量名

初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:cond:要初始化的条件变量。attr:NULL

销毁
int pthread_cond_destroy(pthread_cond_t *cond)
参数:要销毁的条件变量

等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量

唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond); 唤醒所有线程
int pthread_cond_signal(pthread_cond_t *cond); 唤醒一个线程(等待队列中的第一个线程)

用一个线程去控制其他线程示例代码:

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

using namespace std;

pthread_mutex_t mtx;
pthread_cond_t cond;

//ctrl thread 控制work线程,让他定期运行
void *ctrl(void* args)
{
    string name = (char*)args;
    while(true)
    {
        //pthread_cond_signal: 唤醒在条件变量下等待的一个线程,哪一个??
        //在cond 等待队列里等待的第一个线程
        cout << "master say: begain work" << endl;
        
      
        //唤醒一个进程
        pthread_cond_signal(&cond);

        //唤醒所有进程
        //pthread_cond_broadcast(&cond);
        sleep(2);
    }
}

void *work(void* args)
{
    int number = *(int*)args;
    delete (int*)args;

    while(true)
    {
        pthread_cond_wait(&cond, &mtx);
        cout << "worker[" << number << "]is working ..." << endl;
       
    }

}
int main()
{

#define NUM 3

    pthread_mutex_init(&mtx, nullptr);
    pthread_cond_init(&cond, nullptr);

    pthread_t master;
    pthread_t worker[NUM];

    pthread_create(&master, nullptr, ctrl, (void*)"boss");
    for(int i = 0; i < NUM; i++)
    {
        int *number = new int(i);
        pthread_create(worker + i, nullptr, work, (void*)number);

    }
    
    for(int i = 0; i < NUM; i++)
    {
        pthread_join(worker[i], nullptr);
    }

    pthread_join(master,nullptr);
    
    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);

    return 0;
}

运行结果:
在这里插入图片描述
可见我们成功用一个线程,控制了其他线程。并且每回唤醒的都是等待队列中第一个线程

pthread_cond_wait(&cond,&mtx)为什么参数中要有一个互斥锁呢?

因为wait被调用的时候,会首先自动释放锁,然后挂起自己。(没有锁的话,那么他就会抱着锁挂起,程序也就死锁了),返回的时候,会首先自动竞争锁,获取到锁之后才能返回。

生产者消费者模型

为何要使用生产者消费者模型?

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

我们通过日常生活中的一个例子来画图理解一下:

在这里插入图片描述
基于阻塞队列的生产消费模型代码:gitee代码

POSIX信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem:信号量
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量
int sem_destroy(sem_t *sem);

等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);

发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);

基于环形队列的生产消费模型代码:gitee代码

代码实现原理:
在这里插入图片描述

读者写者问题

在这里插入图片描述

读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁.
在这里插入图片描述
注意:写独占,读共享,写锁优先级高

设置读写优先
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁

初始化
头文件:#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
参数:
rwlock:是要进行初始化的锁
attr:是rwlock的属性。此参数一般不关注,可设为NULL

销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:
rwlock:是需要进行销毁的锁

加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 在进行读操作的时候加的锁;
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 在进行写操作的时候加的锁;
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 对读/写统一进行解锁;

<3>线程池

概念

  • 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
    (通俗来说,就是提前准备好一批线程用来随时处理任务,就成为线城池。)

线程池应用场景:

  • 需要大量的线程来完成任务,且完成任务的时间比较短。
  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

普通线程池代码:普通线程池

<4>单例模式

什么是单例模式?

单例模式是一种 “经典的, 常用的, 常考的” 设计模式。某些类, 只应该具有一个对象(实例), 就称之为单例。(构造函数私有化,禁用拷贝构造函数和赋值等于函数)。

什么是设计模式?

大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式。

饿汉实现方式和懒汉实现方式

  • 饿汉方式:在类加载时就完成了初始化,但是加载比较慢,获取对象比较快
  • 懒汉方式:在类加载的时候不被初始化。

二者区别

  • 线程安全:饿汉式在线程还没出现之前就已经实例化了,所以饿汉式一定是线程安全的。
  • 执行效率:饿汉式没有加任何的锁,因此执行效率比较高。懒汉式一般使用都会加同步锁,效率比饿汉式差。
  • 内存使用:饿汉式在一开始类加载的时候就实例化,无论使用与否,都会实例化,所以会占据空间,浪费内存。懒汉式什么时候用就什么时候实例化,不浪费内存。

单例模式线程池代码:单例模式线程池

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/18186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue2.0开发之——Vue基础用法-条件渲染指令(23)

一 概述 条件渲染指令—v-if和v-showv-elsev-else-if 二 条件渲染指令—v-if和v-show 2.1 条件渲染指令 条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个&#xff0c;分别是&#xff1a; v-ifv-show 2.2 示例 布局代码 <div id"a…

【考研复试】计算机专业考研复试英语常见问题五(兴趣爱好/实践经历篇)

相关链接&#xff1a; 【考研复试】计算机专业考研复试英语常见问题一&#xff08;家庭/家乡/学校篇&#xff09;【考研复试】计算机专业考研复试英语常见问题二&#xff08;研究方向/前沿技术/本科毕设篇&#xff09;【考研复试】计算机专业考研复试英语常见问题三&#xff0…

Redhat(10)-防火墙-文件管理-JINJA2模板-Cron-文件权限-NTP-autofs

1.防火墙 2.文件管理 3.JINJA2模板 4.Cron作业 5.文件权限 6.NTP 7.autofs 1.防火墙 网络过滤子系统-netfilter&#xff1a;修正、丢弃数据包。 firewalld是什么&#xff1f; 就是处理网卡来的数据包。 1.源地址被分配给特定区域&#xff0c;应用该区域的规则。 2.网卡…

PyQt5 QLineEdit

PyQt5 QLineEditQLineEdit常用方法及属性QLineEdit 实例1QLineEdit 实例2QLineEdit 实例3QLineEdit 综合示例QLineEdit常用方法及属性 QLineEdit 实例1 import sys from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import *class MyLineEditWindo…

【VTK+有限元后处理】实时剖切视图

目的 实现后处理结果云图的平面剖切视图。 方法 通过使用vtkPlaneWidget控件交互&#xff0c;得到剖切平面&#xff0c;通过vtkClipDataSet完成对vtkUnstructuredGrid有限元结果数据的剖切操作。渲染管线如下图所示[1]^{[1]}[1]。 代码实现 首先&#xff0c;我们先写一个创…

【软考软件评测师】第二十八章 计算机网络(网络设备网络地址)

【软考软件评测师】第二十八章 计算机网络&#xff08;网络设备网络地址&#xff09; 第二十八章 计算机网络&#xff08;网络设备网络地址&#xff09;【软考软件评测师】第二十八章 计算机网络&#xff08;网络设备网络地址&#xff09;第一部分 知识点集锦1.IPv4地址1&#…

Maya 贴图链接检测重链打包插件tjh_lost_textures_finder 1.3.2 更新发布

经常遇到maya工程文件贴图丢失或是路径链接更改的问题&#xff0c;对于贴图师和渲染师来说&#xff0c;海量的贴图重连 贴图和查找丢失贴图都是繁重耗时的工作。自从tjh_lost_textures_finder插件诞生以来&#xff0c;就一直再做此项工作的优化工作。 V1.3.2最新版更新内容&am…

Python爬虫:scrapy从项目创建到部署可视化定时任务运行

目录前言第一节 基本功能1、使用 pyenv创建虚拟环境2、创建 scrapy项目3、创建爬虫第二节 部署爬虫4、启动 scrapyd5、使用 scrapyd-client 部署爬虫项目6、使用 spider-admin-pro管理爬虫第三节 部署优化7、使用 Gunicorn管理应用8、使用 supervisor管理进程9、使用 Nginx转发…

简单shell批量文件转换gbk转为utf8编码

前言 matlab打包成exe时发现中文乱码&#xff0c;查找发现是gbk编码问题,找半天没找到合适的批量转换编码工具&#xff0c;就搞了个简单的shell来实现 准备工作 windows上有安装git bash命令行的话可以直接跑sh脚本,没有的话下一个很快。linux可以直接运行 代码 #!/bin/sh…

Answering the SDIs Step by Step

title: Notes of System Design No.01 —Answering the SDIs Step by Step description: Answering the SDIs Step by Step ’ date: 2022-05-04 14:52:06 tags: 系统设计 categories: 系统设计 In this Article , I will give a introduction to the guildline of answerin…

实验33:RFID门禁卡实验

OK&#xff0c;本实验分为两个部分 一、读卡 二、显示不同的卡的信息&#xff0c;同时继电器动作 01 硬件电路设计 读卡ID&#xff0c;两张卡&#xff0c;白卡和蓝卡&#xff0c;用txt文件名称体现 lib里面是库文件 把它放在自己的Arduino相应的文件家里&#xff0c;最好是…

MySQL数据库 -- 内置函数

今天来一起学习MySQL数据库的内置函数。 目录 日期函数 current_date current_time current_timestamp date_add date_sub datediff 实例演示 创建一张表&#xff0c;记录生日 创建一个留言表 字符串函数 charset concat length replace substring ucase…

第02章 变量

变量介绍 定义 定义&#xff1a;变量是程序的基本组成单位&#xff0c;变&#xff08;变化&#xff09;量&#xff08;值&#xff09; 变量有三个基本要素&#xff1a;数据类型 变量名称 值 变量相当于内存中的一个数据存储空间的表示&#xff0c;可以把变量看作是一个房间…

Pthread 并发编程(三)——深入理解线程取消机制

基本介绍 线程取消机制是 pthread 给我们提供的一种用于取消线程执行的一种机制&#xff0c;这种机制是在线程内部实现的&#xff0c;仅仅能够在共享内存的多线程程序当中使用。 基本使用 #include <stdio.h>#include <pthread.h>#include <assert.h>#incl…

数字化助力生产制造管理:专项生产管理系统

对于现今的生产企业而言&#xff0c;大家都是希望可以让整个制造生产过程更加的规范。同时越来越多的企业也已经分析到&#xff0c;在现有的社会生产方式中&#xff0c;如果不能够使加工业和制造业有先进的更为现代化的管理模式&#xff0c;是难以取得更好的竞争优势的。因此在…

Hadoop搭建

一、安装虚拟机 root 123456789 1、安装完系统后配置虚拟机ip vim /etc/sysconfig/network-scripts/ifcfg-ens33 DEFROUTE"yes" IPV4_FAILURE_FATAL"no" IPV6INIT"yes" IPV6_AUTOCONF"yes" IPV6_DEFROUTE"yes" IPV6_FAIL…

Git学习1

Git学习历程——努力入世的后浪~。 目录 一、Git安装 二、Git的初级使用 1.Git配置文件 2.Git基本命令 2.1 git init 2.2 ls -al 2.3 touch 2.4 git add 2.5 git commit -m 2.6 git status 2.7 git rm 2.8 git log 2.9 git reset --hard 2.10 git reflog 三、Git…

Linux操作系统面试题

Linux 概述 什么是Linux Linux是一套开源的类似Unix操作系统&#xff0c;是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络 为核心的设计思想&#xff0…

pytorch深度学习实战lesson22

第二十二课 LeNet LeNet神经网络由深度学习三巨头之一的Yan LeCun提出&#xff0c;他同时也是卷积神经网络 (CNN&#xff0c;Convolutional Neural Networks)之父。 LeNet主要用来进行手写字符的识别与分类&#xff0c;并在美国的银行中投入了使用。LeNet的实现确立了CNN的结构…

KKFileView在线预览禁用复制右键图片保存等操作

KKFileView在线预览禁用复制右键图片保存等操作一、需求背景二、修改kkFileview1.docx、doc文档不可复制、F12、右键、打印限制问题2.图片限制拖拽处理3.限制Excel转换后复制等操作4.PDF模式禁用右上角菜单栏一、需求背景 公司的运营平台&#xff0c;管理了一些如合同等内容&a…