pthread
查看当前系统线程库
$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.17
POSIX Thread Library (NPTL)使Linux内核可以非常有效的运行使用POSIX线程标准写的程序。
在内核2.6以前的调度实体都是进程,内核并没有真正支持线程。它是能过一个系统调用clone()来实现的,这个调用创建了一份调用进程的拷贝,跟fork()不同的是,这份进程拷贝完全共享了调用进程的地址空间。Linux Thread 就是通过这个系统调用来提供线程在内核级的支持的(许多以前的线程实现都完全是在用户态,内核根本不知道线程的存在)。非常不幸的是,这种方法有相当多的地方没有遵循POSIX标准,特别是在信号处理,调度,进程间通信原语等方面。
很显然,为了改进LinuxThread必须得到内核的支持,并且需要重写线程库。为了实现这个需求,开始有两个相互竞争的项目:IBM启动的NGTP(Next Generation POSIX Threads)项目,以及Redhat公司的NPTL。在2003年的年中,IBM放弃了NGTP,也就是大约那时,Redhat发布了最初的NPTL。
NPTL最开始在redhat linux 9里发布,现在从RHEL3起内核2.6起都支持NPTL,并且完全成了GNU C库的一部分。
准备一个文件a.txt待写入。
$ echo -e "This is a.txt\n" >> a.txt
$ cat a.txt
This is a.txt
$
信号量
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
// 保护文件的信号量
sem_t mysem;
// 新的thread要调用的函数
void *writeFile(void *arg)
{
printf("another thread...\n");
char *filename = (char *)arg;
// 设置线程名字
prctl(PR_SET_NAME,"ANOTHER_THREAD");
// 等待mysem非零,原子操作减一
sem_wait(&mysem);
int fd = open(filename,O_RDWR|O_APPEND);
// 等待3s,模拟其他无关操作
// 出现了粒度的概念,如果这三秒用不到fd,就不应该读取那么早
// 在main函数的while循环中在等待这个被mysem信号量保护的文件
// 它阻止了main中读取文件。
sleep(3);
// 写入一句话
char *buf="thread1 add something...\n";
write(fd,buf,strlen(buf));
printf("fd %d is going to close.\n",fd);
close(fd);
// 信号量+1,不为零也就是广而告之:a.txt写其他东西可用了
sem_post(&mysem);
// 退出线程,参数为返回给pthread_join的参数
pthread_exit((void *)"run");
}
int main()
{
pthread_t another_thread;
char *fname = "./a.txt";
// 初始化信号量,设置为1,表示文件可用,sem_wait不会阻塞
sem_init(&mysem,0,1);
// 创建线程,参数无,线程进入的函数为writeFile,参数为文件名
pthread_create(&another_thread,NULL,writeFile,(void *)fname);
int fd = -1;
int i = 0;
int ret;
while(true)
{
sleep(1);
//sem_wait(&mysem);
// 非阻塞sem_wait,方便我打印一句话提示使用者
// 而不只是无反应的阻塞
ret = sem_trywait(&mysem);
// wait在等待信号量不为0,如果为0的话
// 返回-1,并且错误变量errno设置为EAGAIN
if(ret==-1 && errno==EAGAIN)
{
printf("main func wait %d sec...\n",++i);
continue;
}
// 文件可用了,写一句话代表进去过
fd = open(fname,O_RDWR|O_APPEND);
char *buf="man func add something...\n";
write(fd,buf,strlen(buf));
close(fd);
break;
}
puts("main func while loop exit...\n");
char *msg;
// 等待线程,得到thread的返回参数
pthread_join(another_thread,(void **)&msg);
printf("got thread1: %s\n",msg);
}
打印结果如下:
$ g++ sem_file.cc -pthread
$ ./a.out
another thread...
main func wait 1 sec...
main func wait 2 sec...
main func wait 3 sec...
fd 3 is going to close.
main func while loop exit...
got thread1: run
$ cat a.txt
This is a.txt
thread1 add something...
man func add something...
查看线程名(最后一行),果然已经是我们设置的线程名了。
[hqinglau@centos ~]$ ps -Taux | grep a.out
hqinglau 18220 0.0 0.0 23036 864 pts/0 Sl+ 22:24 0:00 ./a.out
hqinglau 18233 0.0 0.0 112816 992 pts/1 S+ 22:24 0:00 grep --color=auto a.out
[hqinglau@centos ~]$ cat /proc/18220/task/18220/stat
18220 (a.out) S ...
[hqinglau@centos ~]$ cat /proc/18220/task/18221/stat
18221 (ANOTHER_THREAD) S ...
互斥锁和条件变量也差不多是这个用法。下面着重讲下线程池。
线程池
一个基本的思路就是,维护一个任务队列,然后把任务分配给某一个线程,分配机制可以是轮询或者锁竞争等方式。
下面以锁竞争为例讲解一个实例。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <string>
#include <iostream>
#include <queue>
using namespace std;
template <typename T>
class ThreadPool
{
public:
ThreadPool(int n_threads):threadnum(n_threads),stop(false)
{
assert(n_threads>0);
mutex = PTHREAD_MUTEX_INITIALIZER;
threads = new pthread_t[n_threads];
for (int i=0;i<n_threads;i++)
{
pthread_create(threads+i,NULL,worker,this);
if (pthread_detach(threads[i]))
{
delete[] threads;
throw std::exception();
}
}
}
~ThreadPool()
{
delete []threads;
}
bool addTask(T d)
{
pthread_mutex_lock(&mutex);
taskQueue.push(d);
pthread_mutex_unlock(&mutex);
}
private:
static void *worker(void *arg);
void loop()
{
while(!stop)
{
pthread_mutex_lock(&mutex);
if(taskQueue.empty())
{
pthread_mutex_unlock(&mutex);
continue;
}
// 这里一般会是一个接口,也就是一个process函数
// 这边pop之后,直接调用队列中对象的process函数
// 这里简单printf一下
T data = taskQueue.front();
taskQueue.pop();
//display(data);
int fd = open("/home/hqinglau/vscodeFile/server/pthread/a.txt",O_RDWR|O_APPEND);
write(fd,((string)data).c_str(),((string)data).size());
close(fd);
pthread_mutex_unlock(&mutex);
}
}
private:
int threadnum;
pthread_t *threads;
queue<T> taskQueue;
pthread_mutex_t mutex;
bool stop;
};
template <typename T>
void * ThreadPool<T>::worker(void *arg)
{
ThreadPool *p = (ThreadPool *)arg;
p->loop();
}
int main()
{
file_mutex = PTHREAD_MUTEX_INITIALIZER;
ThreadPool<string> *pool = new ThreadPool<string>(3);
char buf[10];
string data;
for(int i=0;i<10;i++)
{
sprintf(buf,"data %d",i);
data = buf;
string d=buf;
pool->addTask(d);
}
while(1);
//delete pool;
return 0;
}
实现功能:写入文件(没必要,只是为了示例)
[hqinglau@centos pthread]$ ./a.out
^C
[hqinglau@centos pthread]$ cat a.txt
data 0data 1data 2data 3data 4data 5data 6data 7data 8data 9