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

2023-11-10

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;
}

} // namespace

int main()
{
	int ival = 1;
	float fval = 10.f;
	char buf[] = "func";
	void* para[3] = { &ival, &fval, buf };

	pthread_t pid = 0; // thread handle
	int 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;
}

} // namespace

int 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;
}

} // namespace

int 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/


GitHubhttps://github.com/fengbingchun/Linux_Code_Test


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

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

  • BeagleBone Black 如何用作大容量存储设备?

    是否可以使用 BB 作为大容量存储设备 我希望将其连接到可以从 USB 连接 例如 USB 闪存驱动器 读取文件的音频播放器并充当包含一个特定文件夹的数据存储设备 及其子文件夹 从文件系统 如果可能 在连接到开发板的闪存驱动器上 正如设备规
  • 通过名称获取进程ID

    我想在 Linux 下获得一个给定其名称的进程 ID 有没有一种简单的方法可以做到这一点 我还没有在 C 上找到任何可以轻松使用的东西 如果追求 易于使用 char buf 512 FILE cmd pipe popen pidof s p
  • GCC 详细模式输出解释

    我是 Linux 新手 谁能向我解释一下我的 hello world 程序的以下详细模式输出 另外 这些文件是做什么用的crt1 o crti o crtend o crtbegin o and crtn o and lc and lgcc
  • 虚拟内存澄清——大连续内存的分配

    我有一个应用程序 我必须在 Windows 上分配 使用运算符 new 相当大的内存空间 数百 MB 该应用程序是 32 位 我们现在不使用 64 位 即使在 64 位系统上也是如此 我启用了 LARGEADDRESSAWARE 链接器选项
  • EULA 接受 Bash 脚本

    我有一个尝试安装垃圾箱的脚本 除了 bin 在 more 中打开 EULA 之外 一切正常 在脚本再次开始并自行完成安装之前 您必须手动 ctrl c 退出此 more 实例 因为这更多的是逃离 shell 所以脚本在打开后不知道要运行什么
  • 每个虚拟主机的错误日志?

    在一台运行 Apache 和 PHP 5 的 Linux 服务器上 我们有多个带有单独日志文件的虚拟主机 我们似乎无法分离 phperror log虚拟主机之间 覆盖此设置
  • 在 Linux 服务器上创建和编辑 MS-Word 文档?

    希望开发处理文档的服务器端应用程序 源文档大多是MS Word 2003 2007 即MS版本的Docx 希望服务器应用程序能够在linux或windows上运行 想知道在linux下读写MS Word文件最好的工具或库是什么 兼容性是最重
  • 在 Windows / Linux 中创建 Mac 包

    我自己努力制作一个 r 包 我按照 stackoverflow 中上一个问题的说明进行操作如何为外行开发软件包 http cran r project org bin windows Rtools 以下是我根据上一个问题采取的步骤 在新的
  • 进程如何知道它已收到信号

    如果我错了 请纠正我 以下是我对信号的理解 据我所知 信号生成 和信号传递有2个不同 事物 为了产生信号 操作系统只是在位数组中设置一个位 在过程控制中维护 工艺块 PCB 每一位 对应于特定信号 当设置一个位时 这意味着 该位对应的信号为
  • sudo pip install python-Levenshtein 失败,错误代码 1

    我正在尝试在 Linux 上安装 python Levenshtein 库 但每当我尝试通过以下方式安装它时 sudo pip install python Levenshtein 我收到此错误 命令 usr bin python c 导入
  • 设置 Vim 背景颜色

    当我尝试更改背景颜色时 vimrc或者直接在 Vim 中使用以下命令 set background dark 这根本不影响我的背景 也没有light选项 不过 当我运行 gvim 时 看起来还不错 有没有办法在不更改 Konsole 设置的
  • 在Linux中创建可执行文件

    我计划做的一件事是编写 非常简单的 Perl 脚本 并且我希望能够在不从终端显式调用 Perl 的情况下运行它们 我明白 要做到这一点 我需要授予他们执行权限 使用 chmod 执行此操作非常简单 但它似乎也是一个稍微费力的额外步骤 我想要
  • 路由是否会影响具有绑定源地址的套接字?

    假设我有两个网络接口 eth0有地址10 0 0 1 eth1有地址192 168 0 1 Using route or ip route add我已将其设置为路由 所有地址至eth0 1 2 3 4只为了eth1 所以数据包到1 2 3
  • Linux 阻塞与非阻塞串行读取

    I have 这段代码 https stackoverflow com questions 6947413 how to open read and write from serial port in c用于在Linux中从串行读取 但我不
  • 期待、互动,然后再次期待

    有几篇关于相同内容的帖子 但我仍然无法使我的期望脚本正常工作 我的目的是自动化一切 但保留用户输入的密码 所以脚本有 3 个部分 自动登录 给予用户交互以输入密码 将控制权交还给 Expect 脚本以继续工作 所以我有一个将生成的脚本 其中
  • C# - OPC-UA 服务器应用程序尚未在 Linux 计算机中创建 PKI 证书

    当我跑步时OPC UA serverWindows 机器中的 C 应用程序 然后 OPC UA 服务器已创建证书路径C ProgramData OPC Foundation pki own 并在此路径中生成一些证书 但是当我在中安装 OPC
  • 在Linux中将日期附加到文件名

    我想在文件名旁边添加日期 somefile txt 例如 somefile 25 11 2009 txt 或 somefile 25Nov2009 txt 或任何类似的内容 也许脚本或终端窗口中的某些命令可以执行 我正在使用Linux Ub
  • 如何从外部模块导出符号?

    我在内核源代码树之外进行编码 有两个模块 第一个printt有一个功能printtty 将字符串打印到当前 tty 以及第二个模块hello这会调用printtty 在初始化期间 我已经添加了EXPORT SYMBOL printtty 在
  • Linux mremap 不释放旧映射?

    我需要一种方法将页面从一个虚拟地址范围复制到另一个虚拟地址范围 而无需实际复制数据 范围很大 延迟很重要 mremap 可以做到这一点 但问题是它也会删除旧的映射 由于我需要在多线程环境中执行此操作 因此我需要旧映射能够同时使用 因此稍后当
  • 我如何知道用户在使用 ncurses (Linux) 的控制台中按下了 ESC 键?

    I have a problem in detecting whether I just got a plain ESC key just code 27 or whether it was another special key such

随机推荐

  • JS 实现全屏切换,移动端适用

    JS 实现全屏切换 移动端适用 直接看代码吧 简单 只是有些人不知道这个 api 我之前就不知道
  • tensorflow搭建自己的残差网络(ResNet)

    废话不说 直接上代码 首先 pip install tflearn 训练代码 coding utf 8 from future import division print function absolute import import tf
  • python HTTP Server 文件上传与下载

    实现在局域网 同一WIFI下 文件上传与下载 该模块通过实现标准GET在BaseHTTPServer上构建 和HEAD请求 将所有代码粘贴到同一个py文件中 即可使用 所需包 基于python3版本实现 python2版本无涉猎 impor
  • LeetCode-1606. 找到处理最多请求的服务器、C++中优先队列的使用

    你有 k 个服务器 编号为 0 到 k 1 它们可以同时处理多个请求组 每个服务器有无穷的计算能力但是 不能同时处理超过一个请求 请求分配到服务器的规则如下 第 i 序号从 0 开始 个请求到达 如果所有服务器都已被占据 那么该请求被舍弃
  • 如何构建用户画像

    从1991年Tim Berners Lee发明了万维网 World Wide Web 开始 到20年后2011年 互联网真正走向了一个新的里程碑 进入了 大数据时代 经历了12 13两年热炒之后 人们逐渐冷静下来 更加聚焦于如何利用大数据挖
  • vue3的hooks和vue2的mixins有什么区别

    接上一篇文章继续解释 语法和用法 Hooks 是在 Vue 3 的 Composition API 中引入的一种函数式编程的方式 而 Mixins 是在 Vue 2 中的一种对象混入机制 Hooks 使用函数的方式定义和使用 而 Mixin
  • Android9自动更新,详解Android app自动更新总结(已适配9.0)

    1 配置 1 1 AndroidManifest xml中添加权限和FileProvider android name androidx core content FileProvider android authorities com f
  • Oracle 数据库密码文件的使用和维护

    Oracle 数据库密码文件的使用和维护 最近在做dataguard测试时 遇到个关于oracle的密码文件的问题 现在就把有关密码文件做下简单的总结 方便以后应对类似的问题 概要 Oracle 关系数据库系统以其卓越的性能获得了广泛的应用
  • 对话MVP

    本期 对话MVP 清华大学软件学院在读博士马福辰将为大家分享他参与社区共建4年以来的成长与蜕变 在这期间 他冲破迷茫 与团队协力开发了面向Solidity合约的安全分析工具SCStudio 并以开源形式贡献给社区 该工具帮助不少开发者检测安
  • 在R语言中,我们经常使用可视化图像来展示数据的分布、关系和趋势

    在R语言中 我们经常使用可视化图像来展示数据的分布 关系和趋势 图像的标题是非常重要的 它能够提供关键信息并帮助读者理解图像的含义 在R中 我们可以使用cex main参数来指定可视化图像标题文本的字体大小 在本文中 我将详细介绍如何使用c
  • springcloud 总 架构图

    版权所有 转载 请表明出处 相关代码地址 欢迎到我的网站寻找更多的知识
  • img图片加载失败?

    问题场景 在工作中经常会使用 lt img gt 标签进行图片展示 但是经常有图片加载失败的情况发生 图片地址不存在 图片已经删除等 场景再现 图片加载失败时的用户体验是很不好的 虽然 lt img gt 标签有alt属性可以展示文本 但是
  • UE4_UnrealBuildTool : error : UBT ERROR: Failed to produce item

    C 编译报错 可能原因 路径太长 包含中文
  • Flink安装部署{单机模式、会话模式(集群部署)、yarn模式(包含hadoop3.1.3部署)}

    flink部署 前置准备 1 CentOS7 5 2 java8 3 配置三台机器时间同步和免密登陆 关闭防火墙 ip地址 主机名 192 168 10 128 master 192 168 10 129 slave1 192 168 10
  • Qt信号和槽的连接方法

    官方文档 查看手册 可以看出 如果想要把信号和槽函数联系起来 通过connect 方法即可 connect 的参数 sender 产生信号的对象 signal 信号的名字 method 槽函数 示例 UdpReceiver UdpRecei
  • spring中Service类中调用this导致@Transaction使用失效的情况

    发现一个有趣的地方 以前在开发中经常遇到的情况 现象 关于expose proxy的解释 this指向目标对象 因此调用this b 将不会执行b事务切面 即不会执行事务增强 spring Transaction 在refresh过程中 通
  • 常见的标识符

    关键字 abstract assert boolean break byte case catch char class const continue default do double else enum extends final fi
  • java后端向前端发送blob类型、arraybuffer类型数据流

    java后端向前端发送blob类型 arraybuffer类型数据流 项目需求概述 前端请求图片时遇到跨域问题 思考解决方案时 考虑前端请求本地javaweb接口时带上图片url 由后端请求到图片后再发送到前端 后端代码 package c
  • Webpack 和 Vite 的区别

    Webpack 和 Vite 都是前端构建工具 但它们在设计哲学和执行方式上有所不同 以下是两者之间的主要区别 基本原理 Webpack 它是一个模块打包工具 它的主要目标是打包 JavaScript 模块 为了处理 ES modules
  • Linux下多线程编程互斥锁和条件变量的简单使用

    Linux下的多线程遵循POSIX线程接口 称为pthread 编写Linux下的多线程程序 需要使用头文件pthread h 链接时需要使用库libpthread a 线程是进程的一个实体 是CPU调度和分派的基本单位 它是比进程更小的能