C/C++基于线程的并发编程(二):线程安全和线程锁

2023-11-12

线程安全

所谓线程安全不是指线程的安全,而是指内存的安全。线程是由进程所承载,所有线程均可访问进程的上下文,意味着所有线程均可访问在进程中的内存空间,这也是线程之间造成问题的潜在原因。当多个线程读取同一片内存空间(变量、对象等)时,不会引起线程安全问题。但是当多个线程对同一片内存空间进行写操作时,就需要考虑内存安全问题。

线程不安全例子

#include <iostream>
#include <thread>

using namespace std;
int counter = 0;

void func()
{
    for (int i = 0; i < 10000; ++i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));	// 休眠1ms
        ++counter;		// 写入操作:counter自加
    }
    
}

int main()
{
    std::thread t1 = std::thread(func);
    std::thread t2 = std::thread(func);

    t1.join();
    t2.join();

    cout << "counter: " << counter << endl;

    return 0;
}

上述代码,预期结果是counter输出20000,因为两个线程均会进入循环,counter各自自加10000次。实际输出效果如下:

counter: 19989
counter: 19992

两次输出结果均不相同,且均不等于预期的20000。这就线程不安全导致的BUG。

线程锁

为了解决上述线程安全问题,就需要线程锁,C和C++最常用的锁包括互斥锁、条件锁、自旋锁和读写锁等。其中最基本的是互斥锁,其他的锁都是基于互斥锁实现。所以锁的功能越强大,性能就越低。

互斥锁

互斥锁(mutex),也成为互斥量,就是保证多线程共享同一个互斥量,然后让线程之间。当一个线程对共享数据操作时,利用互斥锁进行上锁,其他线程在尝试再次上锁时,发现已上锁就会睡眠等待。等到互斥锁解开,才能再次上锁进而继续操作,从而保护共享数据的安全。下面利用互斥锁,解决上述BUG。

Windows环境C++实现

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mutex;

void func()
{
    for (int i = 0; i < 10000; ++i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        mutex.lock();
        ++counter;
        mutex.unlock();
    }
    
}

int main()
{
    std::thread t1 = std::thread(func);
    std::thread t2 = std::thread(func);

    t1.join();
    t2.join();

    std::cout << "counter: " << counter << std::endl;

    return 0;
}

输出结果如下:

counter: 20000
counter: 20000

两次运行输出均为20000,与预期结果相同,证明加入互斥锁后解决线程安全问题。

基于RAII优化C++实现

RAII(Resource Acquisition Is Initialization)是一种C++编程技术,它将必须在使用前请求的资源(例如:分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期与一个对象的生存周期相绑定。 RAII保证资源可用于任何会访问该对象的函数。它亦保证所有资源在其控制对象的生存期结束时,以获取顺序的逆序释放。类似地,若资源获取失败(构造函数以异常退出),则为已构造完成的对象和基类子对象所获取的所有资源,会以初始化顺序的逆序释放。这有效地利用了语言特性以消除内存泄漏并保证异常安全。

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mutex;

void func()
{
    for (int i = 0; i < 10000; ++i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        std::lock_guard<std::mutex> lock(mutex);
        ++counter;
    }
    
}

int main()
{
    std::thread t1 = std::thread(func);
    std::thread t2 = std::thread(func);

    t1.join();
    t2.join();

    std::cout << "counter: " << counter << std::endl;

    return 0;
}

lock_guard使用的就是RAII编程技巧,在方法结束的时候,局部变量std::lock_guard<std::mutex> lock会被销毁,它对互斥体的锁定也就解除了。

Linux环境C语言实现

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

int counter = 0;
pthread_mutex_t mutex;

void *func(void *val)
{
    for (int i = 0; i < 10000; ++i)
    {
        usleep(1 * 1000);
        pthread_mutex_lock(&mutex);
        ++counter;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_mutex_init(&mutex, NULL);

    pthread_t t1;
    pthread_t t2;

    pthread_create(&t1, NULL, func, NULL);
    pthread_create(&t2, NULL, func, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&mutex);

    printf("counter: %d\n", counter);

    return 0;
}

输出如下:

counter: 20000

输出也是预期的20000。

条件锁

条件锁,即条件变量,可以用于阻塞一个线程或多个线程,直到另一个线程修改共享变量(条件)后,通知条件锁。条件锁的实现的基于互斥锁和条件变量共同实现的,也可以实现线程同步。

Windows环境C++实现

C++条件锁依赖于std::unique_lock实现,条件锁需要满足某个条件才会进行加锁,否则将等待直到条件满足,下面举例无法等待成功情况。

条件锁成员函数wait
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mutex;
std::condition_variable condition;
std::string data;
bool ready = false;

void func()
{
    std::unique_lock<std::mutex> locker(mutex);
    condition.wait(locker, []{return ready;});      // 等待ready为true,再上锁

    std::cout << "condition locker finish waiting." << std::endl;
    data += " after waiting locker.";
}

int main()
{
    std::thread thread(func);

    data = "Example data";

	thread.join();
	
    std::cout << data << std::endl;

    return 0;
}

程序无法退出,且无法输出,因为thread在join后,一直没有等待ready置为true,所以停止在func函数的wait调用中无法退出。
若不想一直等待条件符合,可以使用wait_for函数,等一段时间后若条件达成则返回true,未达成则返回false。下面实现例子

条件锁成员函数wait_for
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>

using namespace std::chrono_literals;
std::mutex mutex;
std::condition_variable condition;
std::string data;
bool ready = false;

void func()
{
    std::unique_lock<std::mutex> locker(mutex);
    if( condition.wait_for(locker, 1s, []{return ready;})) {
        std::cout << "condition locker finish waiting." << std::endl;
        data += " after waiting locker.";
    }   
}

int main()
{
    std::thread thread(func);

    data = "Example data";

    thread.join();

    std::cout << data << std::endl;		// 输出: Example data

    return 0;
}

线程等待1s后,未收到ready置为true,输出“Example data”。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>

using namespace std::chrono_literals;
std::mutex mutex;
std::condition_variable condition;
std::string data;
bool ready = false;

void func()
{
    std::unique_lock<std::mutex> locker(mutex);
    if( condition.wait_for(locker, 1s, []{return ready;})) {
        std::cout << "condition locker finish waiting." << std::endl;
        data += " after waiting locker.";
    }   
}

int main()
{
    std::thread thread(func);

    data = "Example data";
    {
        // 线程安全
        std::lock_guard<std::mutex> locker(mutex);
        ready = true;
    }
    condition.notify_one();

    thread.join();

    std::cout << data << std::endl;

    return 0;
}

输出如下:

condition locker finish waiting.
Example data after waiting locker.

当thread线程开启后,主线程将ready置为true,并通过notify_one通知线程。thread的条件锁等待成功,往下运行输出信息与预期相同。
notify_one和notify_all,区别在于notity_one只会唤醒一个线程,notify_all会唤醒所有等待的线程。

Linux环境C语言实现

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <memory.h>

char data[128] = "Example data";
pthread_mutex_t mutex;
pthread_cond_t condition;

void *func(void *val)
{
    pthread_cond_wait(&condition, &mutex);

    printf("func condition locker finish waiting.\n");

    char *temp = "after func waiting locker.";

    strcat(data, temp);
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condition, NULL);

    pthread_t t1;
    pthread_create(&t1, NULL, func, NULL);

    usleep(100 * 1000);     // 等待100ms,防止线程未开始等待就发出条件信号
    pthread_cond_signal(&condition);

    pthread_join(t1, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&condition);
    
    printf("%s\n", data);

    return 0;
}

输出如下:

func condition locker finish waiting.
Example dataafter func waiting locker.

读写锁

读写锁就是区分线程中的读操作和写操作。由于线程中只执行读操作,对临界区时不构成影响,所以区分读操作和写操作,就可以程序的效率。当线程对读写锁加读操作锁时,其他线程也需要读时,可以直接获得独写锁并读取临界区资源;而当其他线程在读写锁加锁的情况下进行写操作锁的加锁,需要等待读写锁释放才能进行获得锁,且写操作锁只能由一个线程拥有。读写锁也被成为共享-独占锁,因为在读操作,读写锁时共享给读操作的,而在写操作时就是独占于一个线程。

Windows环境C++实现

在C++14起加入了std::shared_lockstd::shared_timed_mutex可共享锁的模板,结合std::unique_lock进行读写锁的实现。

#include <shared_mutex>
#include <iostream>
#include <chrono>
#include <thread>

int i = 100;
std::shared_timed_mutex mutex;

void funcRead() 
{
    std::shared_lock<std::shared_timed_mutex> locker(mutex);
    std::cout << std::this_thread::get_id() << "funcRead: " << i << std::endl;
    std::cout << std::this_thread::get_id() << "end funcRead" << std::endl;
}

void funcWrite()
{
    std::unique_lock<std::shared_timed_mutex> locker(mutex);
    std::cout << std::this_thread::get_id() << "funcWrite: " << ++i << std::endl;
}

int main()
{
    std::thread t1Read(funcRead);
    std::thread t2Read(funcRead);
    std::thread t3Write(funcWrite);
    
    t1Read.join();
    t2Read.join();
    t3Write.join();

    return 0;
}
3funcRead: 100
2funcRead: 100
2end funcRead
3end funcRead
4funcWrite: 101

由于线程的调度是内核实现,上述实现为简单实现,线程的开启可能有所不同。如果需要控制线程的开启时机可以加入条件锁进行线程函数的处理。上述输出能证明再两个funcRead线程中,互斥量是共享的,而写锁需要等到读锁释放才能获得。

Linux环境C语言实现

#include <pthread.h>
#include <stdio.h>

int i = 100;
pthread_rwlock_t rwlock;

void *funcRead(void *val)
{
    pthread_rwlock_rdlock(&rwlock);
    printf("thread 0x%x: %d\n", (unsigned int)pthread_self(), i);
    pthread_rwlock_unlock(&rwlock);
    printf("end funcRead\n");
}

void *funcWrite(void *val)
{
    pthread_rwlock_wrlock(&rwlock);
    printf("thread 0x%x: %d\n", (unsigned int)pthread_self(), ++i);
    pthread_rwlock_unlock(&rwlock);
}

int main()
{
    pthread_t t1read;
    pthread_t t2read;
    pthread_t twrite;

    pthread_rwlock_init(&rwlock, NULL);
    pthread_create(&t1read, NULL, funcRead, NULL);
    pthread_create(&t2read, NULL, funcRead, NULL);
    pthread_create(&twrite, NULL, funcWrite, NULL);

    pthread_join(t1read, NULL);
    pthread_join(t2read, NULL);
    pthread_join(twrite, NULL);

    pthread_rwlock_destroy(&rwlock);

    return 0;
}
thread 0xf7d9f700: 100
end funcRead
thread 0xf759e700: 100
end funcRead
thread 0xf6d9d700: 101

C语言实现主要利用POSIX标准的pthread_rwlock_t的读写锁API实现,输出与C++相类似。

自旋锁

自旋锁与互斥锁是类似的,实现方式也是基于互斥锁实现(上文提到,所有复杂的锁都是基于互斥锁实现的)。区别在于自旋锁是不断循环并测试锁的状态,这样会一直占用cpu资源。
C++没有提供自选锁的API,所以下面基于POSIX在C语言中实现自旋锁。

Linux环境C语言实现

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

int counter = 0;
pthread_spinlock_t spinlock;

void *func(void *val)
{
    for (int i = 0; i < 10000; ++i)
    {
        usleep(1 * 1000);
        pthread_spin_lock(&spinlock);
        ++counter;
        pthread_spin_unlock(&spinlock);
    }
}
int main()
{
    pthread_spin_init(&spinlock, 0);

    pthread_t t1;
    pthread_t t2;

    pthread_create(&t1, NULL, func, NULL);
    pthread_create(&t2, NULL, func, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_spin_destroy(&spinlock);

    printf("counter: %d\n", counter);

    return 0;
}

输出和互斥锁的实现一样:

counter: 20000
自旋锁和互斥锁等待锁的资源消耗情况

将互斥锁和自旋锁的线程函数修改为以下状况,让线程一直等待锁。

/* 互斥锁 */
void *func(void *val)
{
    for (int i = 0; i < 10000; ++i)
    {
        usleep(1 * 1000);
        pthread_mutex_lock(&mutex);
        ++counter;
        // pthread_mutex_unlock(&mutex);
    }
}

/* 自旋锁 */
void *func(void *val)
{
    for (int i = 0; i < 10000; ++i)
    {
        usleep(1 * 1000);
        pthread_spin_lock(&spinlock);
        ++counter;
        // pthread_spin_unlock(&spinlock);
    }
}

互斥锁资源消耗情况:
在这里插入图片描述
自旋锁资源消耗情况:在这里插入图片描述从两张图可以看出,当互斥锁等待锁时会将资源交还给CPU,然后等待下次内核调用在尝试获得锁。而自旋锁会一直占用CPU,也就是死循环。所以互斥锁是休眠等待(sleep-waiting),而自旋锁是忙等待(busy-waiting)

递归锁

递归锁就是在同一个线程中,不解锁的情况下,可以重复获得同一个递归锁,而不导致死锁。互斥锁为非递归锁。

Windows环境C++实现

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex mutex;
std::string shared;

void func1()
{
    std::lock_guard<std::recursive_mutex> locker(mutex);
    shared = "func1";
    std::cout << "thread id: " << std::this_thread::get_id() << " func1: shared variable: " << shared << std::endl; 
}

void func2()
{
    std::lock_guard<std::recursive_mutex> locker(mutex);
    shared = "func2";
    std::cout << "thread id: " << std::this_thread::get_id() << " func2: shared variable: " << shared << std::endl;
    func1();
    std::cout << "thread id: " << std::this_thread::get_id() << " back in func2, shared variable: " << shared << std::endl;
}

int main()
{
    std::thread t1(func1);
    std::thread t2(func2);

    t1.join();
    t2.join();
}

输出如下:

thread id: 2 func1: shared variable: func1
thread id: 3 func2: shared variable: func2
thread id: 3 func1: shared variable: func1
thread id: 3 back in func2, shared variable: func1

在func2中,在线程t2重新上锁,递归锁再次获得锁,这就是递归锁的特性。

非递归锁情况

如果将递归锁换成非递归锁,程序如下:

#include <iostream>
#include <thread>
#include <mutex>

// std::recursive_mutex mutex;
std::mutex mutex;
std::string shared;

void func1()
{
    // std::lock_guard<std::recursive_mutex> locker(mutex);
    std::lock_guard<std::mutex> locker(mutex);
    shared = "func1";
    std::cout << "thread id: " << std::this_thread::get_id() << " func1: shared variable: " << shared << std::endl; 
}

void func2()
{
    // std::lock_guard<std::recursive_mutex> locker(mutex);
    std::lock_guard<std::mutex> locker(mutex);
    shared = "func2";
    std::cout << "thread id: " << std::this_thread::get_id() << " func2: shared variable: " << shared << std::endl;
    func1();
    std::cout << "thread id: " << std::this_thread::get_id() << " back in func2, shared variable: " << shared << std::endl;
}

int main()
{
    std::thread t1(func1);
    std::thread t2(func2);

    t1.join();
    t2.join();
}

输出如下:

thread id: 2 func1: shared variable: func1
thread id: 3 func2: shared variable: func2

当线程t2运行到func1时无法获得锁,发生死锁的情况。这就是递归锁和非递归锁的区别。

Linux环境C语言实现

#include <stdio.h>
#include <pthread.h>
#include <string.h>

char shared[12] = {0};
pthread_mutex_t mutex;

void func1()
{
    pthread_mutex_lock(&mutex);
    strcpy(shared, "func1");
    printf("thread id: 0x%x, func1 shared variable: %s\n", (unsigned int)pthread_self(), shared);
    pthread_mutex_unlock(&mutex);
}

void func2()
{
    pthread_mutex_lock(&mutex);
    strcpy(shared, "func2");
    printf("thread id: 0x%x, func2 shared variable: %s\n", (unsigned int)pthread_self(), shared);
    func1();
    printf("thread id: 0x%x, back in func2, shared variable: %s\n", (unsigned int)pthread_self(), shared);
    pthread_mutex_unlock(&mutex);
}

int main()
{
    pthread_t t1;
    pthread_t t2;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    
    pthread_mutex_init(&mutex, &attr);
    pthread_create(&t1, NULL, (void *)func1, NULL);
    pthread_create(&t2, NULL, (void *)func2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&mutex);

    return 0;
}

输出如下:

thread id: 0xf7d9f700, func1 shared variable: func1
thread id: 0xf759e700, func2 shared variable: func2
thread id: 0xf759e700, func1 shared variable: func1
thread id: 0xf759e700, back in func2, shared variable: func1

总结

线程安全: 多线程同时操作同一片临界区时,可能会导致临界区资源改变。所以临界区资源在多线程操作时需要做保护,否则可能导致操作结果错误。解决线程安全的方法就是对资源进行加线程锁。
互斥锁: 互斥锁(mutex)是最基础的线程锁,其他的线程锁均在互斥锁的基础下实现。互斥锁是多个线程共享一个互斥量,当互斥量被设置时,其他线程只能等待互斥量被解放才能继续操作。互斥锁是睡眠等待机制,在等待过程不会占用系统资源。互斥锁是非递归锁。
条件锁: 条件锁(condition_variable)也就条件变量,当线程在条件不满足会进入休眠,一直等待条件变量满足再往下执行。条件锁需要通过notify_one和notify_all唤醒,再执行条件判断。条件锁可以实现线程同步。
读写锁: 读写锁是区分多线程中读操作和写操作的线程锁,也叫共享-独占锁,读锁共享,写锁独占。由于多线程间同时读临界区变量,不会造成线程安全问题,但要保证读操作时不会发生写操作。读写锁在上读锁时,其他线程也可以获取读锁,其他线程想获取写锁时就需要等待读锁解开。当读写锁上写锁时,其他线程无法获取写锁和读锁。
自旋锁: 自旋锁使用方式与互斥锁类似。自旋锁为忙等待机制,自旋锁在等待锁时,会占用CPU,因为自旋锁实际上是循环判断锁的状态。
递归锁: 递归锁的特点是在同一线程可以重复获得锁而不死锁。区分于非递归锁,非递归锁在同一线程重复尝试获得锁时,就会出现死锁。

以上均是个人愚见,如果有哪里说得不对的地方请大家指出。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++基于线程的并发编程(二):线程安全和线程锁 的相关文章

随机推荐

  • 【ChatGPT】500个ChatGPT/GPT-4 Prompt技巧

    文章目录 一 前言 二 什么是Prompt Engineering 三 ChatGPT GPT 4 Prompt Engineering使用技巧 一 前言 随着 GPT 4 和 DALL E等大型 强大的生成式 AI 模型变得更好 更可用
  • 分布式锁-Redisson

    目录 1 分布式并发问题 2 如何解决分布式并发问题呢 3 使 Redis实现分布式锁 代码实现 4 解决因线程异常导致 法释放锁的问题 5 解决因t1过期释放t2锁的问题 6 看 狗机制 7 分布式锁框架 Redisson 7 1 Red
  • MOS管为什么会存在寄生电感

    说到MOS管的寄生参数 我们一般都只想到mos管各极间的寄生电容 很少会想到MOS管的寄生电感 其实分立的MOS管它是存在寄生电感的 并且栅极 源极和漏极都存在 在一些MOS的数据手册会提到这个寄生电感 那么MOS管寄生电感是怎么产生的呢
  • 如何将VB6项目部署到服务器上,如何将整个VB6项目保存到新文件夹?模块和所有...

    Option Explicit Public VBInstance As VBIDE VBE Public Connect As Connect Private Sub CancelButton Click Connect Hide End
  • 使用oracle的trunc和dbms_random.value随机取n条数据

    场景 今天在review项目代码的时候看到这样一个问题 有一张号码表 每次需要从这样表中随机取6个空闲的号码 也就是每次取出来的6个号码应该都会有所不同 然后我就看到了这样的SQL select t from tel number tbl
  • 个人博客--小小笔记2

    目录 一 博客登录 1 JWT技术 03 md 1 1 jwt介绍 1 2 jwt使用 2 利用token JWT 编写登录方法 2 1实现jwt 2 2jwt存放缓存redis中 以便后期检测是否登录 3 统一错误码 减少大量重复繁琐的返
  • jenkins定时构建时间设置

    举几个例子 每隔5分钟构建一次 H 5 每两小时构建一次 H H 2 每天中午12点定时构建一次 H 12 每天下午18点定时构建一次 H 18 在每个小时的前半个小时内的每10分钟 H 0 29 10 每两小时45分钟 从上午9 45开始
  • 蓝牙 BLE 协议学习: 有关概念介绍

    背景 在学校内就用过蓝牙技术参加过比赛 并拿了奖 而蓝牙作为物联网中比较常见的协议 有必要进行深入的学习 此后的文章会以 ble v4 0 进行学习 介绍 蓝牙技术最初由电信巨头爱立信公司于 1994 年创制 当时是作为 RS232 数据线
  • 学计算机编程我有什么好处,编程是什么 学习编程的好处

    在科技快速发展的今天 很多人都会问编程是什么 学习编程有什么好处 下面有途网小编就带大家整理了一下 希望可以给你们带来帮助 编程是什么 编程是编写程序的中文简称 就是让计算机代为解决某个问题 对某个计算体系规定一定的运算方式 使计算体系按照
  • <QT>把日志输出打印在终端

    在 pro文件中 CONFIG行后添加 console CONFIG c 11 console
  • JSON View谷歌浏览器插件使用

    下载JsonView扩展程序压缩包 解压这个压缩包 打开谷歌浏览器的扩展程序界面 方式一 在谷歌浏览器地址栏中输入 chrome extensions 方式二 加载JsonView扩展程序 选中开发者模式 点击 加载正在开发的扩展程序 选择
  • Matlab在一张图上画多条曲线或分别画

    1 在plot曲线时 有时想在一张图上重合画多条曲线 我们只需要在画图命令之前加上hold on就好 比如 t 1 0 1 10 y1 sin 2 pi t y1 cos 2 pi t plot y1 hold on plot y2 运行结
  • 八种排序算法之六:详解归并排序

    归并排序的核心思想首先可以从一个问题入手 如果我们在排序时遇到以下两个数组有序数组 有没有一种最快速的方法排序呢 对我们而言 把两个数组合成有序数列如果用之前的方法在遍历一遍太过麻烦 最快速度方法应该是像榫卯结构一样直接插入 有了思路 接下
  • perplexity 衡量指标_语言模型评价指标Perplexity

    在信息论中 perplexity 困惑度 用来度量一个概率分布或概率模型预测样本的好坏程度 它也可以用来比较两个概率分布或概率模型 应该是比较两者在预测样本上的优劣 低困惑度的概率分布模型或概率模型能更好地预测样本 困惑度越小 句子概率越大
  • locate find whereis which type

    使用locate命令 遇到了这样的情况 当前目录下有一个文件 而使用这个命令时却查找不到这个文件 上网查了一下 找到了原因 就在下面 1 find find是最常见和最强大的查找命令 你可以用它找到任何你想找的文件 find的使用格式如下
  • 游戏开发unity xlua框架知识系列:使用lua时可以进行的性能优化

    插眼 总结 暂无 参考文章 https blog csdn net weixin 43863320 article details 104069514 https blog csdn net suifcd article details 8
  • JAVA高精度乘法模板(大数乘以一个小数)

    1 思路 高精度乘法是大数乘以一个int型的小数 和前面模拟不同 这里不是一位一位的乘 而是a一位乘以整个数b 当a乘到最高位且没有进位就结束了 2 代码模板 方法一 a为大数 倒序存储 b为int型 返回a b的结果 public sta
  • 交通指南系统

    一 实验目的 1 掌握图的基本存储方法 2 掌握有关图的操作算法并用高级语言实现 3 熟练掌握图的两种搜索路径的遍历方法 二 实验内容 假设以一个带权有向图表示某一区域的公交线路网 图中顶点代表一些区域中的重要场所 弧代表已有的公交线路 弧
  • 【经验】工业以太网PROFINET从机(TPS-1)与主机(MCU)通讯初始化时序控制注意事项

    目录 一 TPS 1的复位引脚和主机MCU的复位引脚应连接到系统复位 二 应首先启动从设备 然后启动主机MCU 瑞萨电子TPS 1是支持PROFINET协议的单芯片接口元件 它集成了PROFINET CPU PROFINET协议栈 2通道以
  • C/C++基于线程的并发编程(二):线程安全和线程锁

    线程安全 所谓线程安全不是指线程的安全 而是指内存的安全 线程是由进程所承载 所有线程均可访问进程的上下文 意味着所有线程均可访问在进程中的内存空间 这也是线程之间造成问题的潜在原因 当多个线程读取同一片内存空间 变量 对象等 时 不会引起