目录
1 线程编译
2 线程和进程的区别
3 有关线程操作的函数
4 线程互斥和同步(也叫线程安全)
4.1 互斥
4.2 信号量
4.3 条件变量
5 智能指针与多线程
6 线程池
7 多线程的实现
7.1 c语言实现
7.2 c++实现
7.3 java 实现
8 死锁的调试
正文
1 线程编译 gcc -o pthread -lpthread pthread.c
2 线程和进程的区别
进程 | 线程 | 作用 |
---|---|---|
fork() | pthread_create() | 创建 |
exit() | pthread_exit() | 退出 |
wait()/waitpid() | pthread_join() | 回收资源 |
kill() | pthread _cancel() | 杀死进程、线程 |
getpid() | pthread_self() | 获取当前进程/线程号 |
3 有关线程操作的函数
线程的创建 pthread_create;线程的接收,pthread_join 主线程阻塞接收子线程.pthread_detach子线程脱离主线程.pthread_exit (void *status) 子线程退出;pthread _cancel() A线程结束B线程.
4 线程同步
大部分项目都会用到线程同步,而且下面的3种方法都会用到.
为什么要线程同步?
- 防止多个线程共同访问一个全局变量或者函数产生错误. 多线程访问多一个函数的局部变量会产生错误吗?答案是不会,因为每个线程有自己的栈,局部变量保存在自己的栈中.
- 线程时序控制
4.1 互斥:使用方法:锁.作用:防止多个线程访问互相影响,作用与下面2个不同.
pthread_mutex_lock(&rd);
readCount++;
pthread_mutex_unlock(&rd)
image.gif
具体实例网上很多.
4.2 信号量
作用:互斥和同步.
原理:锁可以理解为信号量为1. 信号量是锁的扩展,允许N个线程同时执行一段代码,但是禁止N+1个线程同时运行.相当于java的CountDownLatch.
1)经典问题:哲学家问题,哲学家只有拿到一双筷子才能吃饭,使用信号量实现线程互斥.
å¾ç¤º
image.gif
å¾ç¤º
image.gif
2) 同步(控制线程时序):一个线程放一个线程取.比如:接收线程接收数据放入queue后sem_post(+1),另一个线程sem_wait(-1)(sem_wait 会阻塞线程)从queue中获取数据.同理, 一个线程业务逻辑处理完将结果放入发送queue,另一个线程发送也一样.
4.3 条件变量
作用: 同步(控制线程时序). 线程A发送cond_signal,线程B 在cond_wait 接收(cond_wait 阻塞线程)。
网上例子也多的是.
其中cond_wait 有潜在的问题。核心是取消点,不是运行到哪句都立刻取消的,是到取消点即函数才取消.
https://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html
5 智能指针与多线程
指针不同于其他类型变量,指针内存在堆不在栈,需要释放.多线程指针释放容易产生错误,比如:在线程A使用,线程B释放的情况.
下面是chenshuo老师写的自定义智能指针. 采用无锁华变成,优化拷贝构造函数和赋值构造函数.
// A simple reference counted smart pointer.
// make use of GCC atomic builtins and C++ move semantics
template<typename T>
class counted_ptr
{
typedef int* counted_ptr::*bool_type;
public:
counted_ptr(T* p = nullptr)
: ptr_(p),
count_(p ? new int(1) : nullptr) { }
counted_ptr(const counted_ptr& rhs) noexcept
: ptr_(rhs.ptr_),
count_(rhs.count_)
{
if (count_)
__atomic_fetch_add(count_, 1, __ATOMIC_SEQ_CST);//原子操作add
}
//右值引用简化深拷贝https://zhuanlan.zhihu.com/p/97128024
counted_ptr(counted_ptr&& rhs) noexcept
: ptr_(rhs.ptr_),
count_(rhs.count_)
{
rhs.ptr_ = nullptr;
rhs.count_ = nullptr;
}
~counted_ptr()
{
//退出子函数则调用析构
reset();
}
counted_ptr& operator=(counted_ptr rhs)
{
swap(rhs);
return *this;
}
T* get() const noexcept
{
return ptr_;
}
void reset()
{
static_assert(sizeof(T) > 0, "T must be complete type");
if (count_)
{
//原子操作;引用计数为0,则释放内存.如果引用计数不为0,则仅仅将当前的指针设置为null.内存中的东东并不释放.
if (__atomic_sub_fetch(count_, 1, __ATOMIC_SEQ_CST) == 0)
{
delete ptr_; //释放
delete count_;
}
ptr_ = nullptr;
count_ = nullptr;
}
}
void swap(counted_ptr& rhs) noexcept
{
T* tp = ptr_;
ptr_ = rhs.ptr_;
/*tp是临时变量,所以退出swap函数的时候rhs.ptr_变成指向Null.提高对rhs中指针的拷贝效率.copy-on-write*/
rhs.ptr_ = tp;
int* tc = count_;
count_ = rhs.count_;
rhs.count_ = tc;
}
T* operator->() const noexcept
{
// assert(ptr_);
return ptr_;
}
T& operator*() const noexcept
{
// assert(ptr_);
return *ptr_;
}
operator bool_type() const noexcept
{
return ptr_ ? &counted_ptr::count_ : nullptr;
}
int use_count() const noexcept
{
return count_ ? __atomic_load_n(count_, __ATOMIC_SEQ_CST) : 0;
}
private:
T* ptr_;
int* count_;
};
image.gif
6 线程池
一个线程放数据
g_async_queue_push(handle->queued_packets, pkt);
//典型的将对象放入线程池,线程池分配一个线程处理对象的queue
g_thread_pool_push(ice_send_thread_pool, handle, NULL);
一个线程发送数据
janus_ice_queued_packet pkt = g_async_queue_try_pop(handle->queued_packets);
while (pkt != NULL) {
int32_t error = janus_send_data(pkt, handle);
pkt = g_async_queue_try_pop(handle->queued_packets);
}
7 多线程的实现
1)多线程参数
a) 传整数.注意是把整数当成地址传递.
例如: int i=5 ;pthread_create(&m_threadId[i],NULL,m_cbf,(void )i).
传递原理: 因为地址可能无效,所以编译的时候会有一条warning.这是一个tricky,把数当成地址传.如(void ) 5表示传地址5.
pthread_create取值:执行函数是参数3. 执行函数不是取地址5中的值,而且将地址5强制转换成整数. 把地址当成数使用,按整数读取func(__args ) { k=(int)__args;} 即k=5;
b) 为什么不直接传整数的地址
int pi=5; 传pi不就完了.
why thread 采用这种形式传参数4呢,而不是存数值5所在地址呢?
因为主线程传的是i的地址,所以每个线程从该地址取值。一定是最后那个5(前面的被冲掉了)
for (i=0;i<10;i++) {
p=&i;
pthread_create(&m_threadId[i],NULL,m_cbf,p);
}
子线程读取k=(int )q; run的结果是主传0-5,结果全是5.是同一个变量的同一个地址。而(void )i,是传的地址0,1,2,3,4,5是传的不同的地址。
2) 向线程参数是传对象
a) c++需要传对象引用,c语言需要传struct地址,否则是传值拷贝.
b) 线程函数是回调函数,所以必须是static 或者全局函数.
对于c语言来说,只有上述两种函数. 对于c++ 创建线程时,线程函数如果要设置成类的成员函数,则必须是静态成员函数,在此函数种不能使用非静态成员变量,如果要使用非静态*成员变量,则一种比较适合线程的方法是:建立线程的时候把this指针传进去.
7.1 c语言实现
void *wait(void *t)
{
long tid;
tid = (long)t;
...
pthread_exit(NULL);
}
rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
rc = pthread_join(threads[i], &status);
image.gif
7.2 c++实现
c++的线程是全局函数std:thread,直接向thread 传执行函数和参数。中的对象和外面的对象不一样.
bar1,bar2,bar3为自定义函数.bar1是成员非静态函数,bar2,bar3是成员静态函数.
thread t1(&foo::bar1, &f, 5); //注意在调用非静态类成员函数时,需要加上实例变量。
thread t2(&foo::bar2, 4);
thread t2(&foo::bar3, &node); //传入引用
image.gif
7.3 java 实现
参见:JAVA多线程之间实现同步+多线程并发同步解决方案 https://blog.csdn.net/yz2015/article/details/79436123
image
image.gif
作者:mfdalf
链接:https://www.jianshu.com/p/00a97bac4913
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。