目录
一、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_STREAM
或SOCK_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。