【第七节】python多线程及网络编程

目录

一、python多线程

1.1 多线程的作用

1.2 python中的 threading 模块

1.3 线程锁

二、python网络编程

2.1 通过socket访问网络

2.2 python2.x中的编码问题

2.3 python3的编码问题


一、python多线程

1.1 多线程的作用

多线程技术在计算机编程中扮演着重要的角色,它主要有以下几个作用:

        1. **提高程序的响应性**:在单线程程序中,如果某个操作需要较长时间,比如读取大文件或进行复杂计算,整个程序会在这段时间内无法响应用户的其他操作。而多线程允许程序在执行耗时操作的同时,其他线程可以继续响应用户输入,从而提高程序的响应速度和用户体验。

        2. **提高资源利用率**:现代计算机通常有多个处理器或核心。多线程允许程序同时运行在多个处理器上,从而更充分地利用系统资源,提高处理速度和效率。

        3. **简化程序结构**:通过将复杂的任务分解为多个并行的线程,可以使程序的逻辑更加清晰和模块化。每个线程负责一部分任务,便于管理和维护。

        4. **实现并发操作**:在某些应用场景中,需要同时进行多个独立的任务,如服务器处理多个客户端请求、图形界面同时响应用户输入和后台数据处理等。多线程使得这些并发操作成为可能。

        5. **提高执行效率**:对于可以并行执行的任务,多线程可以显著减少总的执行时间。例如,在数据处理、图像渲染、科学计算等领域,多线程可以大幅提升处理速度。

        然而,多线程编程也带来了一些挑战,如线程同步问题、死锁风险、资源竞争等,需要开发者仔细设计和实现,以确保程序的正确性和稳定性。

1.2 python中的 threading 模块

        Python中用于多线程编程的内置模块是 `threading`。下面我将详细解释如何使用 `threading` 模块来启动多线程。

### 使用 `threading` 模块启动多线程

1. **导入 `threading` 模块**:

import threading

2. **定义线程函数**:
   这个函数将作为新线程的入口点。

   def my_thread_function(arg1, arg2):
       # 线程执行的代码
       print(f"Thread is running with arguments: {arg1}, {arg2}")

3. **创建 `Thread` 对象**:
   在创建 `Thread` 对象时,可以传入线程函数和参数。

thread = threading.Thread(target=my_thread_function, args=("hello", "world"))

4. **启动线程**:
   调用 `start()` 方法启动线程。

thread.start()

5. **等待线程完成(可选)**:
   如果需要等待线程执行完毕,可以调用 `join()` 方法。

thread.join()

### 完整示例

以下是一个完整的示例,展示了如何使用 `threading` 模块创建和启动多个线程:

import threading

def my_thread_function(arg1, arg2):
    print(f"Thread {threading.current_thread().name} is running with arguments: {arg1}, {arg2}")

# 创建多个线程
threads = []
for i in range(5):
    thread = threading.Thread(target=my_thread_function, args=(f"hello_{i}", f"world_{i}"))
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()

print("All threads have finished.")

        在这个示例中,我们创建了5个线程,每个线程执行 `my_thread_function` 函数,并传递不同的参数。最后,我们使用 `join()` 方法确保主线程等待所有子线程完成后再继续执行。

        通过这种方式,您可以利用 `threading` 模块在Python中实现多线程编程,从而提高程序的并发性和响应性。

1.3 线程锁

        在多线程编程中,所有线程共享代码和数据资源。这种共享性带来了一个主要风险:多个线程可能同时访问和修改同一个变量,导致不可预期的结果。为了解决这一问题,大多数编程语言提供了锁机制来确保线程安全。

### 问题代码示例

import threading

g_Num = 0

def threadProc():
    global g_Num
    for i in range(1000000):
        g_Num = g_Num + 1  # 修改数据

thread1 = threading.Thread(name="hello1", target=threadProc)
thread2 = threading.Thread(name="hello2", target=threadProc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(g_Num)

        在这个示例中,两个线程同时对 `g_Num` 进行递增操作,导致最终打印出来的数字不可预期。

### 使用锁机制解决问题

        为了确保线程安全,可以使用锁来保护对 `g_Num` 的访问。以下是修正后的代码:
 

import threading

lock = threading.Lock()
g_Num = 0

def threadProc():
    global g_Num
    for i in range(1000000):
        lock.acquire()  # 获取锁
        g_Num = g_Num + 1  # 修改数据
        lock.release()  # 释放锁

thread1 = threading.Thread(name="hello1", target=threadProc)
thread2 = threading.Thread(name="hello2", target=threadProc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(g_Num)

        通过使用锁,我们确保在任何时刻只有一个线程能够进入锁定范围并修改 `g_Num`,从而避免了竞态条件,确保最终结果的可预期性。

二、python网络编程

2.1 通过socket访问网络

Python 提供了两种不同层次的网络服务接口:

        1. **低级网络服务**:这一层支持基本的 Socket 功能,它实现了标准的 BSD Sockets API,允许开发者访问底层操作系统 Socket 接口的所有方法,从而进行更底层的网络操作。

        2. **高级网络服务**:这一层包含模块 SocketServer,它提供了一系列服务器中心类,旨在简化网络服务器的开发过程,使得开发者能够更快速地构建网络应用。

**什么是 Socket?**

        Socket,又称为“套接字”,是应用程序用于网络通信的一种抽象。通过 Socket,应用程序可以发送请求或响应网络请求,实现不同主机间或同一台计算机上不同进程间的通信。Socket 是网络编程的基础,它封装了复杂的网络通信细节,使得开发者能够更容易地编写网络应用程序。

socket()函数

Python 中,我们用 socket()函数来创建套接字,语法格式如下:

socket.socket([family[, type[, proto]]])

参数

  • family: 套接字家族可以使 AF_UNIX 或者 AF_INET。
  • type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAMSOCK_DGRAM
  • protocol: 一般不填默认为 0。

Socket 对象(内建)方法

简单示例如下:

服务端代码:

# 导入 socket 模块
import socket

def main():
    print("~~~~~服务端启动~~~~~")

    # 1. 创建 socket
    sSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2. 绑定
    sSock.bind(('192.168.1.125', 1234))  # IP地址 可用本地IP测试: 127.0.0.1

    # 3. 监听
    sSock.listen(5)

    # 4. 处理连接
    cSock, addr = sSock.accept()
    print("客户端连接成功")
    cSock.send(str('欢迎:').encode('utf-8'))

    # 5、6 发送、接收数据
    while True:
        print(cSock.recv(1024).decode('utf-8'))
        inStr = input('>>>: ')
        if inStr == 'quit':
            break
        cSock.send(str(inStr).encode('utf-8'))

    # 7. 关闭套接字
    cSock.close()
    sSock.close()

if __name__ == "__main__":
    main()

客户端代码:

# 导入 socket 模块
import socket

def main():
    print("~~~~~客户端启动~~~~~")

    # 1. 创建 socket
    cSock = socket.socket()

    # 2. 连接服务器
    host = '192.168.1.125'  # IP地址 可用本地IP测试: 127.0.0.1
    port = 1234  # 设置端口号
    cSock.connect((host, port))

    # 3、4 发送、接收数据
    while True:
        print(cSock.recv(1024).decode('utf-8'))
        inStr = input('>: ')
        if inStr == 'quit':
            break
        cSock.send(str(inStr).encode('utf-8'))

    # 5. 关闭套接字
    cSock.close()

if __name__ == "__main__":
    main()

2.2 python2.x中的编码问题

        在Python 2.x版本中,存在两个主要的字符串类:`unicode` 和 `str`,它们都继承自 `basestring`。

        `str` 类是带编码的,默认情况下使用 ASCII 编码。因此,如果你的程序中包含中文字符串,默认情况下会报错。可以通过设置Python 2中的字符默认编码来解决这个问题。

  # coding:utf-8   # 默认使用UTF-8编码
  # coding:gbk     # 默认使用GBK编码

        `unicode` 类是不带编码的,用于表示已知文明中的任何一个字符。需要注意的是,`unicode` 并不是一种编码方式。
  示例:

  ul = u"中国"  # 字符串 unicode类型
  print ul      # 输出: u'\u4e2d\u56fd'
  print len(ul) # 输出: 2

  u2 = u'hello'
  print u2      # 输出: u'hello'
  print len(u2) # 输出: 5

  sl = "中国"   # str 类型,字节串
  print sl      # 输出: '\xd6\xd0\xb9\xfa'(中文 GBK 编码,控制台不指定中文默认 GBK)
  print len(sl) # 输出: 4

  s2 = "hello"
  print s2      # 输出: 'hello'
  print len(s2) # 输出: 5

在Python 2中,可以在字符串和字节串之间进行转换:
- `encode`:将字符串按指定方式进行编码,转换成字节流(`str`),存放在内存中。
- `decode`:将字节流按指定方式进行解码,转换成字符串(`unicode`),用于显示。

可以使用 `chardet` 模块来判断字节流的编码:

import chardet
raw = u'12AB好'
print chardet.detect(raw.encode('utf-8'))  # 输出: {'encoding': 'utf-8', 'confidence': 0.99}
print chardet.detect(raw.encode('gbk'))    # 输出: {'encoding': 'GB2312', 'confidence': 0.99}

        在C++中,多字节集通常是GBK编码,而宽字节集是UTF-16编码。字符使用哪种方式进行编码,就应该使用哪种方式进行解码。以下是几种常见情况:
1. C++端发送过来的是GBK编码,我们需要显示,那么应该使用 `decode("GBK")` 转换成 `unicode` 便于显示。
2. C++端发送过来的是UTF-16编码,我们需要显示,那么应该使用 `decode("UTF-16")` 转换成 `unicode` 便于显示。
3. C++端发送过来的数据需要Python端转发到其他C++端,不需要转换。
4. Python端要直接给C++端发送字符串,那么应该根据C++端使用多字节集还是宽字节集,使用 `encode("GBK")` 或者 `encode("UTF-16")` 之后再发送给C++端。
5. 如果你直接使用字节串,那么应该先使用 `decode("UTF-8")` 转换成 `unicode`,再使用 `encode("GBK")` 或者 `encode("UTF-16")` 再发送给C++端。

示例代码:

c++端代码:

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

// 数据包 1024 + 4 + 4 = 1032
struct NETMSGINFO {
    int MSGTYPE;       // 消息类型
    int nMsgLen;       // 消息大小
    char szMsgBuff[1024]; // 消息内容
};

int main() {
    // 1. 初始化环境
    WSADATA wsd = {};
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
        std::cerr << "WSAStartup failed!" << std::endl;
        return 1;
    }

    // 2. 创建套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        std::cerr << "socket creation failed!" << std::endl;
        WSACleanup();
        return 1;
    }

    // 3. 连接
    sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(0x1234);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
        std::cerr << "connect failed!" << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // 4. 发送数据
    NETMSGINFO netbuf = {};
    netbuf.MSGTYPE = 1; // 可以用宏代替,上线
    strcpy_s(netbuf.szMsgBuff, 1024, "xxx:上线了");
    netbuf.nMsgLen = strlen(netbuf.szMsgBuff); // 字符长度,注意Python中字符不以0结尾
    if (send(sock, (char*)&netbuf, sizeof(netbuf), 0) == SOCKET_ERROR) {
        std::cerr << "send failed!" << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // 5. 接收数据
    if (recv(sock, (char*)&netbuf, sizeof(netbuf), 0) == SOCKET_ERROR) {
        std::cerr << "recv failed!" << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    std::cout << netbuf.szMsgBuff << std::endl;

    // 6. 清理环境
    closesocket(sock);
    WSACleanup();
    return 0;
}

python端代码:

import socket
import struct

def main():
    # 创建TCP套接字
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 绑定端口
    sock.bind(("127.0.0.1", 0x1234))
    
    # 监听
    sock.listen(socket.SOMAXCONN)
    
    # 等待连接
    clientsock, addr = sock.accept()
    print(f"Connection from {addr}")
    
    # 获取消息大小是发送数据结构大小
    Msg = clientsock.recv(1032)
    
    # 进行格式拆包,由于字符长度不确定,暂时不解包消息
    msgtype, nMsgLen = struct.unpack('ii', Msg[0:8])
    
    # 第二次根据长度解包,指定解包字符长度,使用切片方式
    msgbuff, = struct.unpack(f'{nMsgLen}s', Msg[8:8+nMsgLen])
    
    # 字符需要进行解码,因为VS中默认是以GBK编码方式
    print(msgbuff.decode('gbk'))
    
    # 回复客户端消息,构建一个数据包
    # 这个数据需要进行GBK编码,否则VS中解析不了字符
    sendbug = '你好\n'.encode('gbk')
    
    # 打包数据
    msg = struct.pack('ii1024s', 0, len(sendbug), sendbug)
    
    # 发送数据
    clientsock.send(msg)
    
    # 关闭套接字
    clientsock.close()
    sock.close()

if __name__ == "__main__":
    main()

2.3 python3的编码问题

        在Python 3中,默认使用UTF-8编码,并且明确区分了文本字符和二进制数据,分别用`str`和`bytes`类型表示。

s1 = "abc"  # str类型,字符串
s2 = b"abc"  # bytes类型,二进制字节流
s1 = "中国"  # str类型,字符串
s2 = b"中国"  # bytes类型,不支持非ASCII字符,这样会报错

        在Python 2中,`str`类型在Python 3中对应`bytes`类型,表现为字节,转换是通过`encode`方法,用于存储。

        在Python 2中,`Unicode`类型在Python 3中对应`str`类型,表现为字符,转换是通过`decode`方法,用于显示。

`encode`和`decode`方法用于在`str`和`bytes`之间进行转换。

示例:

s = "18CM好棒"
print(s.encode())  # 默认使用UTF-8编码
# 输出: b'18CM\xe5\xa5\xbd\xe6\xa3\x92'
print(s.encode("gbk"))  # 使用GBK编码
# 输出: b'18CM\xba\xc3\xb0\xb2'

print(b'18CM\xe5\xa5\xbd\xe6\xa3\x92'.decode())  # 默认使用UTF-8解码
# 输出: '18CM好棒'
print(b'18CM\xba\xc3\xb0\xb2'.decode("gbk"))  # 使用GBK解码
# 输出: '18CM好棒'

需要注意的是,`encode`和`decode`方法的默认参数都是UTF-8。

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值