pthread を使ったプログラム作成のためのひながた ( Windows & Linux)
目的
スレッド(pthread) 機能のプログラムを Linux, Windows 両対応したソリューションの雛形を作成する。
背景
昨今 Microsoft は, Windows & Linux に対応したマルチプラットフォームな環境を出している。.NET MAUI もそうだし, VC++ 用のパッケージマネージャ vcpkg もそうだ。
vcpkg は Linux におけるシステムコールの一部を移植されたものを VC++ コンパイラでビルドできるようになるパッケージである。
昨今のこの環境により, 移植ハードルが下がり, 平素より 両対応を意識して並列に作成することに対して心理的障害も低くなる。
ところで殆どのC言語コンパイラには ifdef というプリプロセッサマクロを解釈し, コンパイルする対象をマクロの値によって変更できるような機能がある。GCC, Visual Studio, いづれもその機能に対応するため, 両対応のプロジェクトの雛形を作っておきこれを流用することで書き分けが可能となり, 長年この手法が採用され続けている。
vcpkg も便利であるが, このやりかたがマルチプラットフォームにおいて長年に亘って採用されているため, 以下にひな形を示す。
動作環境
ホストOS側 : Windows 11
ゲストOS側: Debian 12.6
VMクライアント: VirtualBox 6.1
VSCode 1.100.2 (Linux 用のビルド・デバッグの場合)
Visual Studio 2022 (Windows 用のビルド・デバッグの場合)
ひな サンプル内容
非同期で TCPソケットで 文字列を別の端末に送信するプログラムである。スレッドは pthread でかき分ける必要がないが,
TCP/IP ソケットにまつわる部分は Windows, Linux でかき分けている。
この zip には, vcpkg で取得した pthread を同梱している.
ビルド命令の入った bash も同梱しているため VSCode から bash を実行すればリモートビルドもただちに可能にしている。
LinuxUniq.cpp
#include "build.h"
#ifdef Linux
void TCPClient(int para){
int sockfd;
struct sockaddr_in addr; // req <netinet/in.h>
// ソケット生成
sockfd = socket( AF_INET, SOCK_STREAM, 0); // socket req <netinet/in.h>
int port = 2000;
addr.sin_family = AF_INET;
addr.sin_port = htons( port );
addr.sin_addr.s_addr = inet_addr("192.168.56.1"); // inet_addr req <arpa/inet.h>
connect( sockfd, (struct sockaddr *)&addr, sizeof( struct sockaddr_in ) ); // <sys/types.h> <sys/socket.h>
send( sockfd, "ABCDEFG", 4, 0 );
printf("th%d inter end: ", para);
//char recvData[1000];
// recv( sockfd, recvData, sizeof(recvData), 0 );
// printf("%s\r\n",recvData);
}
#endif
WindowsUniq.cpp
#include "build.h"
#ifdef Windows
void TCPClient(int para){
WSADATA wsaData;
struct sockaddr_in server;
SOCKET sock;
char buf[32];
// winsock2の初期化
WSAStartup(MAKEWORD(2, 0), &wsaData);
// ソケットの作成
sock = socket(AF_INET, SOCK_STREAM, 0);
// 接続先指定用構造体の準備
server.sin_family = AF_INET;
server.sin_port = htons(12345);
server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// サーバに接続
connect(sock, (struct sockaddr *)&server, sizeof(server));
// サーバからデータを受信
memset(buf, 0, sizeof(buf));
int n = recv(sock, buf, sizeof(buf), 0);
printf("aaaa%d, %s\n", n, buf);
send(sock, "HELLO", 5, 0);
// winsock2の終了処理
WSACleanup();
}
unsigned int sleep(unsigned int seconds){
Sleep(seconds * 1000);
return 0;
}
#endif
build.h
#include
#include
#ifdef __x86_64
#define x64
#endif
#ifdef Windows
#define _WINSOCKAPI_ // windows.hを定義した際に、winsock.hを自動的にインクルードしない
#include // こちらが先
#include
unsigned int sleep(unsigned int seconds); // Linuxの unitstd.h の sleep と同じ形式で使うための宣言
#else
#include
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define Linux
#endif
void TCPClient(int para);
KumaNetCom.cpp
#include "build.h"
void *TaskSleep(void* arg)
{
unsigned int para = *(unsigned int*)arg;
sleep(para);
TCPClient((int)para);
sleep(para);
return 0;
}
void EnvironmentOut(){
#ifdef DEBUG
printf("あDebg\n");
#else
printf("あNON_Debg\n");
#endif
#ifdef Linux
printf("りなっくLINUX\n");
#endif
#ifdef x64
printf("x64\n");
#endif
}
int main()
{
#ifdef Windows
SetConsoleOutputCP(65001);
#endif
EnvironmentOut();
const int cnt=2;
pthread_t th[cnt];
int i;
int arg[cnt]; // スレッドごとの引数を格納する配列
int sleeptime=1; // 各スレッドのスリープ時間
for (i = 1; i < cnt; i++) {
arg[i] = i*sleeptime;
pthread_create(&th[i], NULL,TaskSleep , (void*)&arg[i]);
}
printf("aaa\n");
for (i = 1; i < cnt; i++) {
pthread_join(th[i], NULL);
printf("th%d join\n", i);
}
getchar();
return 0;
}
共有フォルダ設定しておくと便利
デバッグのたびに いちいちファイルをプラットフォーム間でコピーしたりするのは煩雑であるため,
ある程度までは 共有フォルダ設定された Windows & VM(LInux) で開発するのが便利である。