系统架构——多线程的应用

什么是多线程,这在相关计算机原理的书籍里都有介绍,通常所说的多线程是指进程内的多线程,由进程创建一个私有线程表,自行管理自己的线程,这样好处是线程阻塞了,只会挂起进程,而不会影响到整个操作系统的运行。每个线程都有自己的栈,每创建一个线程就会分配一定的资源给线程,这就是为什么说要谨慎使用线程,否则会造成不必要的资源浪费,如果在Windows上,默认线程栈只有1M,不小心还会造成堆栈溢出。

C#、Java的多线程,最终映射到操作系统的本地线程实现。

我们可以通过代码演示一下,单线程和多线程在内存使用上的区别:

#include <iostream>
#include <time.h>
#include <math.h>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
void printSomething(int index) {

	int a = index;
	
	//去掉以下注释,以多线程的方式会报栈溢出
	//char str[1024*996];

	printf("%xd->%d\n", &a, a);
	std::this_thread::sleep_for(1s);
}
int main()
{
	std::cout << "non-thread\n";
	
	
	for (int i = 0; i < 5; i++) {
		//单线程输出相同的变量地址
		printSomething(i);
	}
	std::cout << "thread\n";
	std::thread t1[5];
	for (int i = 0; i < 5; i++) {
		//多线程输出不同的变量地址
		t1[i]=std::thread(printSomething, i);
	}

	int x;
	scanf_s("%d", &x);
}

输出结果:

non-thread
c1aff754d->0
c1aff754d->1
c1aff754d->2
c1aff754d->3
c1aff754d->4
thread
c1bff3a4d->0
c21ff504d->4
c1eff524d->1
c1fff8f4d->2
c20ff3e4d->3

所以对于线程,是不能烂用的,比如一个Socket服务器,通常会用单独线程去处理客户端的连接,如果每个连接都会开一个新的线程,势必对服务器的性能造成影响,因为要不停地分配栈空间,会浪费CPU时间和内存空间。特别是长连接的情况下,如果缓存中一直没有数据,那关联的线程就一直挂在那边,不做任何事,白浪费的资源。这种情况下,就会用到线程的另一种机制——线程池。可以让一个线程遍历所有的Socket,装有数据的扔给一个空闲的线程去处理就行了。

我们可以预先设置一组线程池,Windows上提供了一组Api用于实现线程池,原理就是将创建好的线程(绑定一个空函数)放在那等待,如果有任务来了,则发送一个解锁信号,领到任务的执行任务,没领到的继续等待。下边以Windows Api举个例子。

#include <iostream>
#include <time.h>
#include <math.h>
#include <thread>
#include <chrono>
#include <vector>
#include <queue>
#include <Windows.h>

using namespace std::chrono_literals;

const int len = 30;
HANDLE handle[len];//事件通知
int data[len];//测试数据
//任务
void printSomething(int index) {

	int a = index;
	auto tid = std::this_thread::get_id();
	printf("%d:%xd->%d\n", tid, &a, a);
	std::this_thread::sleep_for(200ms);
}
//任务回调
VOID CALLBACK workCall(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) {
	int* index = (int*)Context;
	printSomething(*index);
	SetEvent(handle[*index]);
}

int main()
{
	std::cout << "non-thread\n";
	for (int i = 0; i < len; i++) {
		//单线程输出相同的变量地址
		printSomething(i);
	}
	std::cout << "thread\n";
	TP_CALLBACK_ENVIRON CallBackEnviron;
	InitializeThreadpoolEnvironment(&CallBackEnviron);
	auto pool = CreateThreadpool(NULL);
	SetThreadpoolThreadMinimum(pool, 2);
	SetThreadpoolThreadMaximum(pool, 2);


	for (int i = 0; i < len; i++) {
		data[i] = i;
		handle[i] = CreateEvent(NULL, false, false, NULL);
		auto work = CreateThreadpoolWork(workCall, &data[i], &CallBackEnviron);
		//多线程输出不同的变量地址
		SubmitThreadpoolWork(work);
	}

    //等待所有线程结束
	WaitForMultipleObjects(5, handle, true, INFINITE);
	CloseThreadpool(pool);
	char x=getchar();
}

输出结果:

non-thread
57320:bf56fa84d->0
57320:bf56fa84d->1
57320:bf56fa84d->2
57320:bf56fa84d->3
57320:bf56fa84d->4
57320:bf56fa84d->5
57320:bf56fa84d->6
57320:bf56fa84d->7
57320:bf56fa84d->8
57320:bf56fa84d->9
57320:bf56fa84d->10
57320:bf56fa84d->11
57320:bf56fa84d->12
57320:bf56fa84d->13
57320:bf56fa84d->14
57320:bf56fa84d->15
57320:bf56fa84d->16
57320:bf56fa84d->17
57320:bf56fa84d->18
57320:bf56fa84d->19
57320:bf56fa84d->20
57320:bf56fa84d->21
57320:bf56fa84d->22
57320:bf56fa84d->23
57320:bf56fa84d->24
57320:bf56fa84d->25
57320:bf56fa84d->26
57320:bf56fa84d->27
57320:bf56fa84d->28
57320:bf56fa84d->29
thread
51292:bfcff574d->0
22580:bfdff834d->1
29532:bfeff3d4d->2
81420:bffff184d->3
80700:c00ff684d->4
51292:bfcff574d->5
22580:bfdff834d->6
29532:bfeff3d4d->7
81420:bffff184d->8
77684:c01ff3d4d->9
80700:c00ff684d->10
51292:bfcff574d->11
22580:bfdff834d->12
29532:bfeff3d4d->13
81420:bffff184d->14
54804:bf8ff644d->15
77684:c01ff3d4d->16
80700:c00ff684d->17
51292:bfcff574d->18
22580:bfdff834d->19
29532:bfeff3d4d->20
81420:bffff184d->21
56548:bfbff154d->22
54804:bf8ff644d->23
77684:c01ff3d4d->24
80700:c00ff684d->25
57176:c02ff264d->26
51292:bfcff574d->27
22580:bfdff834d->28
29532:bfeff3d4d->29

从输出结果中的线程id,可以看出线程被重复使用了,栈空间的指针也是相同的。

附C#示例:

using System;
using System.Threading;

public class Example 
{
    public static void Main() 
    {
        // Queue the task.
        ThreadPool.QueueUserWorkItem(ThreadProc);
        Console.WriteLine("Main thread does some work, then sleeps.");
        Thread.Sleep(1000);

        Console.WriteLine("Main thread exits.");
    }

    // This thread procedure performs the task.
    static void ThreadProc(Object stateInfo) 
    {
        // No state object was passed to QueueUserWorkItem, so stateInfo is null.
        Console.WriteLine("Hello from the thread pool.");
    }
}
// The example displays output like the following:
//       Main thread does some work, then sleeps.
//       Hello from the thread pool.
//       Main thread exits.

猜你喜欢

转载自www.cnblogs.com/icoolno1/p/12814142.html