Python против C# в многопоточности - действительно ли GIL влияет на Python
Я прочитал несколько постов ( здесь и здесь), в которых говорится, что многопоточная система Python на самом деле не является "многопоточной" из-за механизма GIL.
Из своего опыта работы с Python я не помню, чтобы я видел большую разницу, поэтому я хотел проверить это на C#.
Я создал аналогичные программы на Python и C#, которые используют 4 потока, которые читают из очереди со списком ссылок от "google.com/0" до "google.com/99". Каждый поток читает ссылку из очереди и отправляет запрос GET. Всего 100 GET запросов.
Я ожидал, что Python должен быть медленнее, потому что он не использует настоящий "многопоточный" механизм из-за GIL.
После того, как я запустил обе программы, я был удивлен, что Python был быстрее.
Питон: 12 секунд.
C#: 27 секунд.
Так влияет ли механизм GIL на Python или нет? Кажется, что он делает многопоточность лучше, чем C#.
Программа на Python: multithread.py
import time
import urllib3
import queue
import threading
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
https = urllib3.PoolManager()
# Taken from https://stackru.com/questions/18466079/can-i-change-the-connection-pool-size-for-pythons-requests-module
def patch_https_connection_pool(**constructor_kwargs):
"""
This allows to override the default parameters of the
HTTPConnectionPool constructor.
For example, to increase the poolsize to fix problems
with "HttpSConnectionPool is full, discarding connection"
call this function with maxsize=16 (or whatever size
you want to give to the connection pool)
"""
from urllib3 import connectionpool, poolmanager
class MyHTTPSConnectionPool(connectionpool.HTTPSConnectionPool):
def __init__(self, *args,**kwargs):
kwargs.update(constructor_kwargs)
super(MyHTTPSConnectionPool, self).__init__(*args,**kwargs)
poolmanager.pool_classes_by_scheme['https'] = MyHTTPSConnectionPool
patch_https_connection_pool(maxsize=160)
def try_get_request(some_string):
# I put google.com as an example but it is an internal server
url = 'https://google.com/' + some_string
r = https.request('GET', url, headers={
'Content-Type': 'application/json'
})
return r.status
def queue_algo():
NUM_CONCURRENT_REQUESTS = 4
work = queue.Queue()
global end_process
end_process = False
def worker():
global end_process
url = r'https://google.com'
#print('****** TID %s: STARTED ******' % threading.get_ident())
while not end_process:
try:
some_string = work.get(True, 3)
#print("TID %s: Testing %s" % (threading.get_ident(), some_string))
status = try_get_request(url + '/' + some_string)
#print("TID %s: DONE %s" % (threading.get_ident(), some_string))
continue
except queue.Empty:
continue
work.task_done()
threads = []
for unused_index in range(NUM_CONCURRENT_REQUESTS):
thread = threading.Thread(target=worker)
thread.daemon = True
thread.start()
threads.append(thread)
queued = 0
wait = False
try:
# The strings I am sending
size = 400
for i in range(0, size):
work.put(str(i))
queued += 1
if queued >= size:
wait = True
break
if wait:
while work.qsize() > 0:
time.sleep(5)
end_process = True
for thread in threads:
if thread.is_alive():
thread.join()
except KeyboardInterrupt:
end_process = True
for thread in threads:
if thread.is_alive():
thread.join()
start_time = time.time()
queue_algo()
print("--- %s seconds ---" % (time.time() - start_time))
Программа на C#: multithread.cs
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace PlayGround
{
class Program
{
static void Main(string[] args)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
Run();
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
Console.WriteLine("[*] Elapsed time: {0}", elapsedMs / 1000);
Console.ReadLine();
}
static BlockingCollection<string> inputQueue = new BlockingCollection<string>();
// https://stackru.com/a/22689821/2153777
private static void Run()
{
string url = @"https://google.com";
string res = Get(url);
int threadCount = 4;
Task[] workers = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
int workerId = i;
Task task = new Task(() => worker(workerId));
workers[i] = task;
task.Start();
}
for (int i = 0; i < 400; ++i)
{
//Console.WriteLine("Queueing work item {0}", url + "/" + i);
inputQueue.Add(url + "/" + i);
}
Console.WriteLine("Stopping adding.");
inputQueue.CompleteAdding();
Task.WaitAll(workers);
Console.WriteLine("Done.");
}
static void worker(int workerId)
{
Console.WriteLine("Worker {0} is starting.", workerId);
foreach (var workItem in inputQueue.GetConsumingEnumerable())
{
string res = "";
//Console.WriteLine("Worker {0} is processing item {1}", workerId, workItem);
try
{
res = Get(workItem);
}
catch (Exception)
{
res = "404";
}
//Console.WriteLine("Worker {0} is processing item {1} with result {2}", workerId, workItem, res);
}
Console.WriteLine("Worker {0} is stopping.", workerId);
}
// https://stackru.com/a/27108442/2153777
public static string Get(string uri)
{
HttpStatusCode status;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
status = response.StatusCode;
}
return status.ToString() + "; Thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
}
}
}