当前位置: 首页 > 编程日记 > 正文

Linux下多线程编程互斥锁和条件变量的简单使用

Linux下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其它的线程共享进程所拥有的全部资源。当多个任务可以并行执行时,可以为每个任务启动一个线程。

线程是并发运行的。在串行程序基础上引入线程和进程是为了提供程序的并发度,从而提高程序运行效率和响应时间。

与进程相比,线程的优势:(1)、线程共享相同的内存空间,不同的线程可以存取内存中的同一个变量;(2)、与标准fork()相比,线程带来的开销很小,节省了CPU时间,使得线程创建比新进程创建快上十到一百倍。

适应多线程的理由:(1)、和进程相比,它是一种非常“节俭”的多任务操作方式,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间;(2)、线程间方便的通信机制。对不同的进程来说,它们具有独立的数据空间,要进行数据的传输只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

多线程程序作为一种多任务、并发的工作方式,其优点包括:(1)、提供应用程序响应;(2)、使多CPU系统更加有效:操作系统会保证当线程数不大于CPU数目时,不同的线程运行在不同的CPU上;(3)、改善程序结构:一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序利于理解和修改。

pthread_create:用于在调用的进程中创建一个新的线程。它有四个参数,第一个参数为指向线程标识符指针;第二个参数用来设置线程属性;第三个参数是线程运行函数的起始地址;第四个参数是运行函数的参数。

在一个线程中调用pthread_create函数创建新的线程后,当前线程从pthread_create处继续往下执行。pthread_create函数的第三个参数为新创建线程的入口函数的起始地址,此函数接收一个参数,是通过第四个参数传递给它的,该参数的类型是void*,这个指针按什么类型解释由调用者自己定义,入口函数的返回值类型也是void*,这个指针的含义同样由调用者自己定义,入口函数返回时,这个线程就退出了,其它线程可以调用pthread_join函数得到入口函数的返回值。

pthread_join:线程阻塞函数,用于阻塞当前的线程,直到另外一个线程运行结束;使一个线程等待另一个线程结束;让主线程阻塞在这个地方等待子线程结束;代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了,加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

pthread_create将一个线程拆分为两个,pthread_join()将两个线程合并为一个线程。

一个线程实际上就是一个函数,创建后,立即被执行,当函数返回时该线程也就结束了

线程终止时,一个需要注意的问题是线程间的同步问题。一般情况下,进程中各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其它线程,终止的线程所占用的资源不会随着线程的终止而归还系统,而是仍然为线程所在的进程持有。一个线程仅允许一个线程使用pthread_join等待它的终止,并且被等待的线程应该处于可join状态,而非DETACHED状态。一个可”join”的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄露,所有线程终止时,要么设为DETACHED,要么使用pthread_join来回收资源。一个线程不能被多个线程等待

所有线程都有一个线程号,也就是threadid,其类型为pthread_t,通过调用pthread_self函数可以获得自身的线程号。

Linux线程同步的几种基本方式:join、互斥锁(mutex)、读写锁(read-writelock)、条件变量(condition variables)。mutex的本质是锁,而条件变量的本质是等待

互斥:简单的理解就是,一个线程进入工作区后,如果有其它线程想要进入工作区,它就会进入等待状态,要等待工作区内的线程结束后才可以进入。

互斥提供线程间资源的独占访问控制。它是一个简单的锁,只有持有它的线程才可以释放那个互斥。它确保了它们正在访问的共享资源的完整性,因为在同一时刻只允许一个线程访问它。

互斥操作,就是对某段代码或某个变量修改的时候只能有一个线程在执行这段代码,其它线程不能同时进入这段代码或同时修改该变量。这个代码或变量称为临界资源。

通过锁机制实现线程间的同步,同一时刻只允许一个线程执行一个关键部分的代码。

有两种方式创建互斥锁,静态方式和动态方式。

在默认情况下,Linux下的同一线程无法对同一互斥锁进行递归加锁,否则将发生死锁。所谓递归加锁,就是在同一线程中试图对互斥锁进行两次或两次以上的行为。解决问题的方法就是显示地在互斥变量初始化时将其设置成recursive属性。

互斥量是一种用于多线程中的同步访问的方法,它允许程序锁住某个对象或者某段代码,使得每次只能有一个线程访问它。为了控制对关键对象或者代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁。

互斥量用pthread_mutex_t数据类型来表示,在使用互斥变量之前,必须首先对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(如调用malloc)函数,那么释放内存前(free)需要使用pthread_mutex_destroy函数。

对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。

pthread_mutex_init函数:主要用于多线程中互斥锁的初始化。如果要用默认的属性初始化互斥量,只需把第二个参数设置为NULL。互斥量的属性可以分为四种:(1)、PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁,这种锁策略保证了资源分配的公平性;(2)、PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争;(3)、PTHREAD_MUTEX_ERRORCHECK_NP,检错,如果该互斥量已经被上锁,那么后续的上锁将会失败而不会阻塞,否则与PTHREAD_MUTEX_TIMED_NP类型相同,这样就保证当不允许多次加锁时不会出现最简单情况下的死锁;(4)、PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

pthread_mutex_destroy函数:销毁(注销)线程互斥锁;销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。

pthread_mutex_lock:占有互斥锁(阻塞操作);互斥锁被锁定,如果这个互斥锁被一个线程锁定和拥有,那么另一个线程要调用这个函数就会进入阻塞状态(即等待状态),直到互斥锁被释放为止;互斥量一旦被上锁后,其它线程如果想给该互斥量上锁,那么就会阻塞在这个操作上,如果在此之前该互斥量已经被其它线程上锁,那么该操作将会一直阻塞在这个地方,直到获得该锁为止。

pthread_mutex_unlock:释放互斥锁;在操作完成后,必须调用该函数给互斥量解锁,这样其它等待该锁的线程才有机会获得该锁,否则其它线程将会永远阻塞。

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分:条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”而挂起;另一个线程使“条件成立”(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两线程共享可读写的内存,条件变量可以被用来实现这两线程间的线程同步。

互斥锁一个明显的缺点是它只有两种状态,锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般来说,条件变量被用来进行线程间的同步。条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。

条件变量用pthread_cond_t结构体来表示。

pthread_cond_init:初始化一个条件变量,当第二个参数属性为空指针时,函数创建的是一个缺省的条件变量,否则条件变量的属性将由第二个参数的属性值来决定。不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

pthread_cond_wait:阻塞在条件变量上,函数将解锁第二个参数指向的互斥锁,并使当前线程阻塞在第一个参数指向的条件变量上。被阻塞的线程可以被pthread_cond_signal、pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。

一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait函数返回之前,条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

pthread_cond_wait函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

pthread_cond_signal:解除在条件变量上的阻塞。此函数被用来释放被阻塞在指定条件变量上的一个线程。一般在互斥锁的保护下使用相应的条件变量,否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定。

pthread_cond_timewait:阻塞直到指定时间。函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由第三个参数指定。

pthread_cond_broadcast:释放阻塞的所有线程。函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程。当没有线程阻塞在这个条件变量上时,此函数无效。此函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁。

pthread_cond_destroy:释放条件变量。条件变量占用的空间未被释放。

pthread_cond_wait和pthread_cond_timewait一定要在mutex的锁定区域内使用;而pthread_cond_signal和pthread_cond_broadcoast无需考虑调用线程是否是mutex的拥有者,可以在lock与unlock以外的区域调用。

一个特定条件只能有一个互斥对象,而且条件变量应该表示互斥数据“内部”的一种特殊的条件更改。一个互斥对象可以有许多条件变量,但每个条件变量只能有一个互斥对象。

以上所有线程相关函数,函数执行成功时返回0,返回其它非0值表示错误。

以下是一些测试例子:

1. test_create_thread.cpp:

#include <pthread.h>
#include <iostream>
#include <unistd.h>namespace {void* run1(void* para)
{	sleep(3);std::cout << "start new thread!" << std::endl;fprintf(stdout, "new thread id: %ld, Line: %d\n", pthread_self(), __LINE__);	int* iptr = (int*)((void**)para)[0];float* fptr = (float*)((void**)para)[1];char* str = (char*)((void**)para)[2];std::cout << *iptr << "    " << *fptr << "    " << str << std::endl;std::cout << "end new thread!" << std::endl;return nullptr;
}void run2(void* para)
{std::cout << "start main thread!" << std::endl;fprintf(stdout, "main thread id: %ld, Line: %d\n", pthread_self(), __LINE__);	int* iptr = (int*)((void**)para)[0];float* fptr = (float*)((void**)para)[1];char* str = (char*)((void**)para)[2];std::cout << *iptr << "    " << *fptr << "    " << str << std::endl;std::cout << "end main thread!" << std::endl;
}} // namespaceint main()
{int ival = 1;float fval = 10.f;char buf[] = "func";void* para[3] = { &ival, &fval, buf };pthread_t pid = 0; // thread handleint err = pthread_create(&pid, nullptr, run1, para);if (err != 0) {std::cout << "can't create thread!" << std::endl;return -1;}fprintf(stdout, "pid: %ld, Line: %d\n", pid, __LINE__); // = new thread id// 新线程创建之后主线程如何运行: 主线程按顺序继续执行下一行程序std::cout << "main thread!" << std::endl;fprintf(stdout, "main thread id: %ld, Line: %d\n", pthread_self(), __LINE__);run2(para);// 新线程结束时如何处理: 新线程先停止,然后作为其清理过程的一部分,等待与另一个线程合并或“连接”pthread_join(pid, nullptr);std::cout << "ok!" << std::endl;return 0;
}// 终端执行: $ g++ -o test_create_thread test_create_thread.cpp -lpthread
//  	   $ ./test_create_thread

2. test_thread_mutex.cpp:

#include <pthread.h>
#include <iostream>
#include <unistd.h>namespace {pthread_mutex_t lock;void* run(void* arg)
{pthread_mutex_lock(&lock);sleep(2);fprintf(stdout, "thread id: %ld, Line: %d\n", pthread_self(), __LINE__);	static int counter = 0;++counter;std::cout << "Job " << counter << " started!" << std::endl;for (unsigned long i = 0; i<(0xFFFFFFFF); ++i);std::cout << "Job " << counter << " finished!" << std::endl;pthread_mutex_unlock(&lock);return nullptr;
}} // namespaceint main()
{fprintf(stdout, "main thread id: %ld, Line: %d\n", pthread_self(), __LINE__);	if (pthread_mutex_init(&lock, nullptr) != 0) {std::cout << "mutex init failed" << std::endl;return -1;}int i = 0;pthread_t tid[2] = {0, 0};while (i < 2) {if (pthread_create(&(tid[i]), nullptr, &run, nullptr) != 0) {std::cout << "can't create thread!" << std::endl;return -1;}++i;}fprintf(stdout, "new thread id: %ld, %ld\n", tid[0], tid[1]);run(nullptr);for (auto pth : tid) {pthread_join(pth, nullptr);}pthread_mutex_destroy(&lock);std::cout << "ok!" << std::endl;fprintf(stdout, "main thread id: %ld, Line: %d\n", pthread_self(), __LINE__);	return 0;
}// 终端执行: $ g++ -o test_thread_mutex test_thread_mutex.cpp -lpthread
//	       $ ./test_thread_mutex

3. test_thread_cond.cpp:

#include <pthread.h>
#include <iostream>
#include <unistd.h>// reference: https://stackoverflow.com/questions/16522858/understanding-of-pthread-cond-wait-and-pthread-cond-signal
namespace {pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
bool flag = false;void* decrement_count(void* arg)
{pthread_mutex_lock(&count_lock);std::cout << "----- decrement_count before cond_wait" << std::endl;while (!flag) {pthread_cond_wait(&count_nonzero, &count_lock);}std::cout << "----- decrement_count after cond_wait" << std::endl;std::cout << "do something that requires holding the mutex and condition is true" << std::endl;flag = false;pthread_mutex_unlock(&count_lock);return nullptr;
}void* increment_count(void* arg)
{pthread_mutex_lock(&count_lock);std::cout << "+++++ increment_count before cond_signal" << std::endl;pthread_cond_signal(&count_nonzero); std::cout << "+++++ increment_count after cond_signal" << std::endl;pthread_mutex_unlock(&count_lock);return nullptr;
}} // namespace int main()
{pthread_t tid[2] = {0, 0};pthread_mutex_init(&count_lock, nullptr);pthread_cond_init(&count_nonzero, nullptr);pthread_create(&tid[0], nullptr, decrement_count, nullptr);pthread_create(&tid[1], nullptr, increment_count, nullptr);sleep(5);flag = true;pthread_cond_signal(&count_nonzero);for (auto pth : tid) {fprintf(stdout, "new thread id: %ld, Line: %d\n", pth, __LINE__);pthread_join(pth, nullptr);}pthread_mutex_destroy(&count_lock);pthread_cond_destroy(&count_nonzero);std::cout << "ok!" << std::endl;return 0;
}// 终端执行:$ g++ -o test_thread_cond test_thread_cond.cpp -lpthread
//	      $ ./test_thread_cond

4. test_thread_cond1.cpp:

#include <pthread.h>
#include <iostream>
#include <unistd.h>namespace {pthread_mutex_t counter_lock;
pthread_cond_t counter_nonzero;
int counter = 0;void* decrement_counter(void* argv)
{std::cout << "counter(decrement): " << counter << std::endl;pthread_mutex_lock(&counter_lock);while (counter == 0)pthread_cond_wait(&counter_nonzero, &counter_lock); // 进入阻塞(wait),等待激活(signal)std::cout << "counter--(decrement, before): " << counter << std::endl;counter--; // 等待signal激活后再执行  std::cout << "counter--(decrement, after): " << counter << std::endl;pthread_mutex_unlock(&counter_lock);return nullptr;
}void* increment_counter(void* argv)
{std::cout << "counter(increment): " << counter << std::endl;pthread_mutex_lock(&counter_lock); // 注意:若此处没有锁,pthread_cond_wait将一直处于无限期阻塞状态if (counter == 0)pthread_cond_signal(&counter_nonzero); // 激活(signal)阻塞(wait)的线程(先执行完signal线程,然后再执行wait线程)  sleep(5);std::cout << "counter++(increment, before): " << counter << std::endl;counter++;std::cout << "counter++(increment, after): " << counter << std::endl;pthread_mutex_unlock(&counter_lock);return nullptr;
}} // namespaceint main()
{std::cout << "counter: " << counter << std::endl;pthread_mutex_init(&counter_lock, nullptr);pthread_cond_init(&counter_nonzero, nullptr);pthread_t thd1, thd2;int ret = -1;ret = pthread_create(&thd1, nullptr, decrement_counter, nullptr);if (ret) {std::cout << "create thread1 fail" << std::endl;return -1;}ret = pthread_create(&thd2, nullptr, increment_counter, nullptr);if (ret) {std::cout << "create thread2 fail" << std::endl;return -1;}pthread_join(thd1, nullptr);pthread_join(thd2, nullptr);pthread_mutex_destroy(&counter_lock);pthread_cond_destroy(&counter_nonzero);std::cout << "ok!" << std::endl;return 0;
}// 终端执行: $ g++ -o test_thread_cond1 test_thread_cond1.cpp -lpthread
//	       $ ./test_thread_cond1
注:以上内容来自于网络整理。

参考文献:

1.      https://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/

2.      http://blog.csdn.net/ithomer/article/details/6031723

3.      https://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/


GitHub:https://github.com/fengbingchun/Linux_Code_Test


相关文章:

03基于python玩转人工智能最火框架之TensorFlow介绍

一句话介绍: Google开源的基于数据流图的科学计算库&#xff0c;适用于机器学习 不局限于机器学习&#xff0c;但目前被大多用于机器学习等。 TensorFlow计算流图的概念图 Tensor在图中流动。 TensorFlow的含义 拆字释义: Tensor 张量(tf中数据的表征) flow 流动 张量在图中流…

赴约北大,2019 CCF大数据与计算智能大赛正式启动

8月17日&#xff0c;以“数据驱动&#xff0c;智创未来”为主题的2019 CCF大数据与计算智能大赛&#xff08;2019 CCF BDCI&#xff09;全球启动仪式&#xff0c;在北京大学英杰交流中心阳光厅正式启幕。自2013年创办以来&#xff0c;大赛已成功举办六届&#xff0c;连续获得教…

Hadoop入门(10)_通过java代码实现从本地的文件上传到Hadoop的文件系统

2019独角兽企业重金招聘Python工程师标准>>> 第一步&#xff1a;首先搭建java的编译环境。创建一个Java Project工程&#xff0c;名为upload。 第二步&#xff1a;选中所需的Jar包。 选中JRE System Library 选择BuildPath Configure Build Path 选择ha…

Caffe源码中各种依赖库的作用及简单使用

1. Boost库&#xff1a;它是一个可移植、跨平台&#xff0c;提供源代码的C库&#xff0c;作为标准库的后备。 在Caffe中用到的Boost头文件包括&#xff1a; (1)、shared_ptr.hpp&#xff1a;智能指针&#xff0c;使用它可以不需要考虑内存释放的问题&#xff1b; (2)、dat…

漫画:5分钟了解什么是动态规划?

作者 | 调皮的阿广来源 | 视学算法&#xff08;ID&#xff1a;z872561826&#xff09;动态规划&#xff0c;英文是Dynamic Programming&#xff0c;简称DP&#xff0c;擅长解决“多阶段决策问题”&#xff0c;利用各个阶段阶段的递推关系&#xff0c;逐个确定每个阶段的最优决策…

小程序大转盘红包雨营销组件

前言 商城没几个营销活动能叫商城吗&#xff1f;所以就来几个组件吧&#xff0c;写的不好轻踩&#xff0c;对你有帮助记得给个小星星哦直接上链接github链接 运行例子 git clone https://github.com/sunnie1992/soul-weapp.git 微信开发者工具打开项目 营销组件 大转盘 "p…

Windows Server 2012 RDS系列:虚拟桌面化(5)

概述&#xff1a;本次将系列地测试Windows Server 2012 远程桌面服务&#xff08;RDS&#xff09;&#xff0c;将过程进行分享&#xff0c;总的感觉比2008 R2更简单了&#xff0c;体现着2012的自动化。2012的RDS部署有标准部署和快速启动两种&#xff0c;快速启动就是自动快速配…

里程碑式成果Faster RCNN复现难?我们试了一下 | 附完整代码

作者 | 已退逼乎 来源 | 知乎【导读】2019年以来&#xff0c;除各AI 大厂私有网络范围外&#xff0c;MaskRCNN&#xff0c;CascadeRCNN 成为了支撑很多业务得以开展的基础&#xff0c;而以 Faster RCNN 为基础去复现其他的检测网络既省时又省力&#xff0c;也算得上是里程碑性成…

【跃迁之路】【725天】程序员高效学习方法论探索系列(实验阶段482-2019.2.15)...

实验说明 从2017.10.6起&#xff0c;开启这个系列&#xff0c;目标只有一个&#xff1a;探索新的学习方法&#xff0c;实现跃迁式成长实验期2年&#xff08;2017.10.06 - 2019.10.06&#xff09;我将以自己为实验对象。我将开源我的学习方法&#xff0c;方法不断更新迭代&#…

C/C++各种数据类型转换汇总

以下是Windows/Linux系统中常用的C/C各种数据类型转换汇总&#xff1a;#ifndef FBC_MESSY_TEST_DATA_TYPE_CONVERT_HPP_ #define FBC_MESSY_TEST_DATA_TYPE_CONVERT_HPP_#include <stdio.h> #include <stdlib.h> #include <iostream> #include <string>…

ASP.NET技巧:两个截取字符串的实用方法

两个截取字符串的实用方法&#xff08;超过一定长度自动换行&#xff09;1/** <summary> 2 /// 截取字符串&#xff0c;不限制字符串长度 3 /// </summary> 4 /// <param name"str">待截取的字符串</param> 5 /…

吃瓜腾讯平均月薪7.27万后,微信又出大招

腾讯最新财报一出&#xff0c;喜提热搜&#xff01;据腾讯第二季度财报显示&#xff1a;2019 年上半年腾讯有员工56310人&#xff0c;总薪酬成本为242.59亿元&#xff0c;腾讯员工平均半年薪为43.08万元。在第一季度里&#xff0c;腾讯员工平均季度薪资为21.27万元&#xff0c;…

回调函数在C/C++中的使用

回调函数就是一个通过函数指针调用的函数。假如把A函数的指针当作参数传给B函数,然后在B函数中通过A函数传进来的这个指针调用A函数&#xff0c;那么就是回调机制。A函数就是回调函数&#xff0c;而通常情况下&#xff0c;A函数是在系统符合你设定的条件下自动执行。使用回调函…

excel单元格加引号及逗号,转换为sql需要的样式

A1 B1BXQY001 ------> BXQY001,BXQY001 -----> BXQY001 在B1中输入公式&#xff1a; ""&A1&""&"," 在B2中输入公式&#xff1a; ""&A1&"" 去掉了后面的逗号。其实就是 " "&A1&…

Win7/Win8 系统下安装Oracle 10g 提示“程序异常终止,发生未知错误”的解决方法...

我的Oracle 10g版本是10.2.0.1.0&#xff0c;&#xff08;10.1同理&#xff09;选择高级安装&#xff0c;提示“程序异常终止&#xff0c;发生未知错误”。1.修改Oracle 10G\database\stage\prereq\db\refhost.xml当打开refhost.xml 后会发现有</SYSTEM> <CERTIFIED…

Caffe基础介绍

Caffe的全称应该是Convolutional Architecture for Fast Feature Embedding&#xff0c;它是一个清晰、高效的深度学习框架&#xff0c;它是开源的&#xff0c;核心语言是C&#xff0c;它支持命令行、Python和Matlab接口&#xff0c;它既可以在CPU上运行也可以在GPU上运行。它的…

飞桨博士会第三期来啦!中国深度学习技术俱乐部诚邀您加入

飞桨博士会是由百度开源深度学习平台飞桨&#xff08;PaddlePaddle&#xff09;发起的中国深度学习技术俱乐部&#xff0c;旨在打造深度学习核心开发者交流圈&#xff0c;助力会员拓展行业高端人脉、交流前沿技术。俱乐部为会员制&#xff0c;成员皆为博士生导师或博士&#xf…

canvas 拼图

效果 代码 <!DOCTYPE html> <html lang"zh_CN"> <head><meta charset"UTF-8"><title>拼图</title><script src"https://code.jquery.com/jquery-3.3.1.js"></script> </head> <body&g…

性能优化之Java(Android)代码优化

最新最准确内容建议直接访问原文&#xff1a;性能优化之Java(Android)代码优化 本文为Android性能优化的第三篇——Java(Android)代码优化。主要介绍Java代码中性能优化方式及网络优化&#xff0c;包括缓存、异步、延迟、数据存储、算法、JNI、逻辑等优化方式。(时间仓促&#…

1小时上手MaskRCNN·Keras开源实战 | 深度应用

作者 | 小宋是呢来源 | CSDN博客0. 前言介绍开源地址&#xff1a;https://github.com/matterport/Mask_RCNN个人主页&#xff1a;http://www.yansongsong.cn/MaskRCNN 是何恺明基于以往的 faster rcnn 架构提出的新的卷积网络&#xff0c;一举完成了 object instance segmentat…

MNIST数据库介绍及转换

MNIST数据库介绍&#xff1a;MNIST是一个手写数字数据库&#xff0c;它有60000个训练样本集和10000个测试样本集。它是NIST数据库的一个子集。MNIST数据库官方网址为&#xff1a;http://yann.lecun.com/exdb/mnist/ &#xff0c;也可以在windows下直接下载&#xff0c;train-im…

PostgreSQL学习笔记(1)

安装psql brew install postgresql 启动服务 brew services start postgresql 使用psql进入控制台&#xff0c;报错&#xff1a; psql: FATAL: database "<user>" does not exist 看来是没有给我的当前用户创建数据库&#xff0c;使用下面命令进入名为templat…

怎样使一个Android应用不被杀死?(整理)

2019独角兽企业重金招聘Python工程师标准>>> 方法 &#xff1a; 对于一个service&#xff0c;可以首先把它设为在前台运行&#xff1a; public void MyService.onCreate() { super.onCreate(); Notification notification new Notification(android.R.drawable.my_…

Ubuntu 14.04 64位机上用Caffe+MNIST训练Lenet网络操作步骤

1. 将终端定位到Caffe根目录&#xff1b; 2. 下载MNIST数据库并解压缩&#xff1a;$ ./data/mnist/get_mnist.sh 3. 将其转换成Lmdb数据库格式&#xff1a;$ ./examples/mnist/create_mnist.sh 执行完此shell脚本后&#xff0c;会在./examples/mnist下增加两个新…

IJCAI 2019:中国团队录取论文超三成,北大、南大榜上有名

作者 | 神经小姐姐来源 | HyperAI超神经&#xff08; ID: HyperAI )【导读】AI 顶会 IJCAI 2019 已于 8 月 16 日圆满落幕。在连续 7 天的技术盛会中&#xff0c;与会者在工作坊了解了 AI 技术在各个领域的应用场景&#xff0c;聆听了 AI 界前辈的主题演讲&#xff0c;还有机会…

适合小小白的完整建设流程

时常有中小企业建站的客户问到我要自己建网站&#xff0c;应该怎么开始&#xff1f;建站有一定的技术门槛&#xff0c;首先要明白建站要做的哪些事情&#xff0c;里面有哪些坑&#xff0c;把流程弄清楚了才能避免入坑&#xff0c;半途而废&#xff01;下面总结了建站的流程还有…

ios项目文件结构 目录的整理

2019独角兽企业重金招聘Python工程师标准>>> /<ProjectName>/Shared/Application # App delegate and related files/Controllers # Base view controllers/Models # Models, Core Data schema etc/Views # Shared views/Libr…

重磅!全球首个可视化联邦学习产品与联邦pipeline生产服务上线

【导读】作为全球首个联邦学习工业级技术框架&#xff0c;FATE支持联邦学习架构体系与各种机器学习算法的安全计算&#xff0c;实现了基于同态加密和多方计算&#xff08;MPC&#xff09;的安全计算协议&#xff0c;能够帮助多个组织机构在符合数据安全和政府法规前提下&#x…

SpringBoot之集成swagger2

maven配置 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.5.0</version> </dependency> <dependency><groupId>io.springfox</groupId><artifact…

Windows Caffe中MNIST数据格式转换实现

Caffe源码中src/caffe/caffe/examples/mnist/convert_mnist_data.cpp提供的实现代码并不能直接在Windows下运行&#xff0c;这里在源码的基础上进行了改写&#xff0c;使其可以直接在Windows 64位上直接运行&#xff0c;改写代码如下&#xff1a;#include "stdafx.h"…