项目学习地址:【牛客网C++服务器项目学习】
函数:void pthread_exit(void *retval);
功能:终止调用这个函数的线程。如果是一个进程的最后一个线程调用该函数,那么该进程会执行调用exit(3)终止该进程。此外,一个线程终止了,并不会影响线程共享的资源:文件描述符等。
参数:
返回值:没有返回值,这个函数总会执行成功
函数:int pthread_equal(pthread_t t1, pthread_t t2);
函数:pthread_t pthread_self(void);
函数:int pthread_join(pthread_t thread, void **retval);
功能:阻塞函数,等待thread线程号对应的线程退出结束,回收资源。
参数:
返回值:成功回收,返回0;执行失败,返回一个 错误号
函数:int pthread_detach(pthread_t thread);
函数:int pthread_cancel(pthread_t thread);
函数:int pthread_attr_init(pthread_attr_t *attr);
函数:int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
一个进程可以创建多少个线程呢?
32位的linux,默认配置下大概是300个
64位的linux,理论个数为1000多w个,实际上会系统参数的性能的限制,我的服务器也就是1w1多个
Linux操作系统里一个进程最多可以创建多少个线程?
1.线程同步问题
互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:创建并初始化互斥锁,上锁、解锁。
lock函数:每当有线程对一个互斥量进行加锁,互斥量计数加1;每当有线程对一个互斥量解锁,互斥量计数减1。只有当该互斥量的计数等于0时,该线程才有资格访问被锁住的内容;否则就是原地阻塞。如果互斥量计数为0,还继续解锁,将会返回一个错误。
UNLOCK函数:对互斥量解锁,当互斥量的计数等于0,由操作系统决定,调度哪一个因为互斥量被阻塞的线程。
trylock函数:lock函数和tryLock函数都是用于锁定对象,但他们之间有一定的区别:lock函数是阻塞的,因为它调用WaitForSingleObject函数时传递的第二个参数是INFINITE,表示无限等待下去,所以是阻塞的。tryLock函数时非阻塞的,调用后立即返回。因为它调用WaitForSingleObject函数时传递的第二个参数是0,表示不等待,立即返回。
返回值:成功返回0,失败返回错误号。
读写锁:
在进行读操作的时候加的锁:
pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
在进行写操作的时候加的锁:
pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
对读/写统一进行解锁:
pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
读写锁其实还是一种锁,是给一段临界区代码加锁,但是此加锁是在进行写操作的时候才会互斥,而在进行读的时候是可以共享的进行访问临界区的 ps:读写锁本质上是一种自旋锁
有时候,在多线程中,有一些公共数据修改的机会比较少,而读的机会却是非常多的,此公共数据的操作基本都是读,如果每次操作都给此段代码加锁,太浪费时间了而且也很浪费资源,降低程序的效率,因为读操作不会修改数据,只是做一些查询,所以在读的时候不用给此段代码加锁,可以共享的访问,只有涉及到写的时候,互斥的访问就好了
读写之间是互斥的—–>读的时候写阻塞,写的时候读阻塞,而且读和写在竞争锁的时候,写会优先得到锁
自旋锁的优缺点
优点:效率高,避免了线程之间调度的开销
缺点:浪费CPU资源
挂起等待锁的优缺点
优点: 不会浪费CPU的资源,比较灵活
缺点:效率不高,很可能会使临界区的代码不被任何线程执行,因为可能会是线程被CPU调度走了但是却没有被调度回来
2.生产者/消费者模型:
3.条件变量:
条件变量是利用线程间共享的全局变量进行同步的一种机制。 主要包括两个动作:
一个线程等待”条件变量的条件成立”而挂起;
另一个线程使”条件成立”(给出条件成立信号)。
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。 条件变量类型为 pthread_cond_t。
函数:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
函数:
相关文章
4.信号量
一、什么是信号量
二、信号量的接口和使用
信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件 semaphore.h中。
1、sem_init函数:该函数用于创建信号量,其原型如下:
2、sem_wait函数:该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:
3、sem_post函数:该函数用于以原子操作的方式将信号量的值加1。它的原型如下:
4、sem_destroy函数:该函数用于对用完的信号量的清理。它的原型如下:
关于信号量,自己在linux0.11上实现过自定义的信号量函数,使用开关中断完成函数的原子操作。文章连接
——————
2021/12/12更新
写了一个小demo,测试了互斥锁和读写锁,对大量读操作的效率差别:
/*
此程序是为了比较
互斥锁和读写锁的在 高并发读操作上的效率差异
创建两个进程,父进程中使用互斥锁读取,子进程使用读写锁读取
每个进程中国,创建10个读线程,对全局变量区的某个变量进行读取
每个线程读取 COUNT 次,总计完成10*COUNT次读取
利用 gettimeofday 函数,对整个读取过程精确到微秒的计时
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
//创建互斥锁
pthread_mutex_t mutex;
//创建读写锁
pthread_rwlock_t rwlock;
//全局变量,父子进程、线程间都是相同的
int data = 123456;
//读取次数
long COUNT = 1000000;
//互斥读函数
void *mFunc(void *arg)
{
int read = 0;
for (long i = 0; i < COUNT; ++i)
{
//加锁
pthread_mutex_lock(&mutex);
read = data;
//解锁
pthread_mutex_unlock(&mutex);
}
//结束线程
pthread_exit(NULL);
}
//读写锁读
void *rwFunc(void *arg)
{
int read = 0;
for (long i = 0; i < COUNT; ++i)
{
//加锁
pthread_rwlock_rdlock(&rwlock);
read = data;
//解锁
pthread_rwlock_unlock(&rwlock);
}
//结束线程
pthread_exit(NULL);
}
int main()
{
//创建两个进程,父进程中使用互斥锁读取,子进程使用读写锁读取
int pid = fork();
if (pid > 0)
{
//parent process
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
//创建十个线程,并开始计时
pthread_t mtids[100];
struct timeval start;
gettimeofday(&start, NULL);
for (int i = 0; i < 100; ++i)
{
pthread_create(&mtids[i], NULL, mFunc, NULL);
}
//在主线程中,调用join函数,回收线程,线程回收完成后,结束计时
for (int i = 0; i < 100; ++i)
{
pthread_join(mtids[i], NULL);
}
struct timeval end;
gettimeofday(&end, NULL);
long timediff = (end.tv_sec - start.tv_sec) * 1000000 + end.tv_usec - start.tv_usec;
printf("互斥锁 读全部线程执行完毕,总耗时: %ld us\n", timediff);
//回收子进程
wait(NULL);
}
else if (pid == 0)
{
//子进程
//初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
//创建
//创建十个线程,并开始计时
pthread_t rwtids[100];
struct timeval start;
gettimeofday(&start, NULL);
for (int i = 0; i < 100; ++i)
{
pthread_create(&rwtids[i], NULL, rwFunc, NULL);
}
//在主线程中,调用join函数,回收线程,线程回收完成后,结束计时
for (int i = 0; i < 100; ++i)
{
pthread_join(rwtids[i], NULL);
}
struct timeval end;
gettimeofday(&end, NULL);
long timediff = (end.tv_sec - start.tv_sec) * 1000000 + end.tv_usec - start.tv_usec;
printf("读写锁 读全部线程执行完毕,总耗时: %ld us\n", timediff);
//结束进程
exit(0);
}
return 0;
}
此外,还用信号量函数,写了一个生产者消费者小demo
/*
使用linux线程库的信号量函数,完成经典的生产者和消费者模型
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//缓冲区
int *buf;
int bufSize = 100;
//三个信号量
sem_t full, empty, mutex;
int bufPtr;
int count;
//生产者线程
void *producer(void *arg)
{
while (bufPtr < bufSize)
{
//信号量模型
sem_wait(&full);
sem_wait(&mutex);
buf[++bufPtr] = bufPtr;
sem_post(&mutex);
sem_post(&empty);
}
}
//消费者线程
void *consumer(void *arg)
{
while (1)
{
//信号量模型
sem_wait(&empty);
sem_wait(&mutex);
count = (count + 1) % __INT32_MAX__;
printf("pid[%ld], count[%d], data[%d]\n", pthread_self(), count, buf[bufPtr--]);
sem_post(&mutex);
sem_post(&full);
}
}
int main()
{
//初始化三个信号量
sem_init(&full, 0, bufSize);
sem_init(&empty, 0, 0);
sem_init(&mutex, 0, 1);
//初始化读写指针、缓冲区
bufPtr = -1;
count = 0;
buf = (int *)malloc(sizeof(int) * bufSize);
//创建6个线程,一个作生产者,5个消费者
pthread_t ppid, cpids[5];
pthread_create(&ppid, NULL, producer, NULL);
for (int i = 0; i < 5; ++i)
{
pthread_create(&cpids[i], NULL, consumer, NULL);
}
//detach分离,线程自动回收资源
pthread_detach(ppid);
for (int i = 0; i < 5; ++i)
{
pthread_detach(cpids[i]);
}
//主线程结束
pthread_exit(NULL);
return 0;
}