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

0 ответов

Другие вопросы по тегам